动态检索数据类型

时间:2016-01-23 18:54:27

标签: haskell generic-programming

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

  • 我可以使用toConstr toConstr (Place "Some place" "Bla")检索其构造函数,这将给我Place
  • 我可以使用constrFields constrFields $ toConstr (Place "Some place" "Bla")检索其标签字段,这样我就会["name", "description"]

现在我需要的是获取我构建Place的值,所以对于Place "Some place" "Bla"我想提取类似["Some place", "Bla"]的东西,我的代码中的抓点是我不喜欢我们不知道这个数据值是Place ,它可以是派生Data类的任何数据类型。 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不必返回完全[String]类型,只需要将值打包在某些内容中。

1 个答案:

答案 0 :(得分:4)

使用cast家庭中的Typeable(请回想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]

采用动态类型编程语言。

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

λ> :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"]
  

注意:getValuesOfDataValue不必返回完全[String]类型,只需要将值打包在某些内容中。

问题是要将其打包。gshow的工作方式是递归调用gmapQ(及其助手extQ,它是从gmapQ构建的cast值上的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的基本情况,因此每当它命中一个字符串时它就知道返回它并终止。在不知道有关您的问题域的更多详细信息的情况下,我会告诉您使用相同的策略出去构建自己的gshows。在您想要打包每个类型的一般情况下,没有答案,但可能存在特定任务的具体内容。