动态检索数据类型的值

Dynamically retrieve values of a data type

我正在使用 Data.Data 模块在运行时动态获取某种数据类型的一些数据。假设一个数据类型为 data Place = Place {name :: Text, description :: Text} deriving (Data):

现在我需要的是获取构建 Place 所用的值,因此对于 Place "Some place" "Bla" 我想提取类似 ["Some place", "Bla"] 的内容,问题在于我的代码 [=32] =]我不知道这个数据值是Place,它可以是派生Dataclass的任何数据类型。 icn码:

getValuesOfDataValue :: (Data a) => a -> [String]
getValuesOfDataValue a =
    -- some magic generic function

data Place = Place {name :: Text, description :: Text} deriving (Data)
 -- the code below should evaluate to ["Some place", "Bla"]
getValuesOfDataValue (Place "Some place" "Bla")

data SomeType = SomeType {num :: Integer, num2 :: Integer} deriving (Data)
 -- the code below should evaluate to [300, 500]
getValuesOfDataValue (SomeType 300 500)

我怎样才能做到这一点?

注意:getValuesOfDataValue 不必 return 完全是一个 [String] 类型,它只需要将值打包在某些东西中。

使用 Typeable 家族的 cast(回想一下 DataTypeable 的子类)。

λ> import Data.Text
λ> import Data.Data
λ> :set -XDeriveDataTypeable -XOverloadedStrings
λ> data Triple = Triple Text Text Int deriving (Show, Data)
λ> gmapQ cast (Triple "a" "b" 1821) :: [Maybe Text]
[Just "a",Just "b",Nothing]
λ> gmapQ cast (Triple "a" "b" 1821) :: [Maybe Int]
[Nothing,Nothing,Just 1821]

接受动态类型编程语言。

如果您事先不知道您想要哪种类型,您也可以使用 syb 包中的 gshow 对值进行字符串化:

λ> :set -package syb
λ> import Data.Generics.Text
λ> data Triple = Triple Text Text Int deriving (Data)
λ> gmapQ gshow (Triple "a" "b" 1821)
["(pack \"a\")","(pack \"b\")","(1821)"]

不过我会警告你:事先不知道你想要哪种类型会严重限制你使用泛型的能力。并不是所有的东西都可以被字符串化,即使它们是丑陋的(如上所示)。即使知道您想要的类型的白名单也会对您有很大帮助:

λ> import Control.Arrow
λ> :set -XScopedTypeVariables
λ> let show' (proxy :: Proxy a) = Kleisli (\x -> show <$> (cast x :: Maybe a))
λ> gmapQ (runKleisli (show' (Proxy :: Proxy Int) <+> show' (Proxy :: Proxy Text))) (Triple "a" "b" 1821)
["a","b","1821"]

Note: getValuesOfDataValue doesn't have to return exactly a [String] type, it just need to have the values packed in something.

问题是将它打包成什么。gshow 的工作方式是它递归调用 gmapQ(及其助手 extQ,它是从 gmapQcast) 在 Data a => a 值上:

-- | Generic show: an alternative to \"deriving Show\"
gshow :: Data a => a -> String
gshow x = gshows x ""

-- | Generic shows
gshows :: Data a => a -> ShowS

-- This is a prefix-show using surrounding "(" and ")",
-- where we recurse into subterms with gmapQ.
gshows = ( \t ->
                showChar '('
              . (showString . showConstr . toConstr $ t)
              . (foldr (.) id . gmapQ ((showChar ' ' .) . gshows) $ t)
              . showChar ')'
         ) `extQ` (shows :: String -> ShowS)

它有一个 String -> ShowS 的基本情况,所以每当它遇到一个字符串时,它就知道 return 它并终止。在不知道有关您的问题域的更多详细信息的情况下,我会建议您使用相同的策略出去构建您自己的 gshows。在您想要打包 每种 类型的非常普遍的情况下,没有答案,但也许存在针对您的特定任务的特定内容。