haskell – 如何在运行时读取类型的元数据?

我想编写一个打印出Haskell类型的元数据的程序.虽然我知道这不是有效的代码,但是这个想法是这样的:

data Person = Person { name :: String, age :: Int }

metadata :: Type -> String
metadata t = ???

metadata Person -- returns "Person (name,age)"

重要的限制是我没有一个Person的实例,只是这个类型.

我已经开始研究泛型和可打印/数据,但没有实例我不知道他们会做什么我需要的.任何人都可以指向正确的方向吗?

Haskell中的反射使用Typeable类,它在Data.Typeable中定义,并包含typeOf *方法来获取值类型的运行时表示.

ghci> :m +Data.Typeable
ghci> :t typeOf 'a'
typeOf 'a' :: TypeRep
ghci> typeOf 'a'  -- We could use any value of type Char and get the same result
Char  -- the `Show` instance of `TypeRep` just returns the name of the type

如果要使Typeable适用于您自己的类型,则可以让编译器使用DeriveDataTypeable扩展名为您生成一个实例.

{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
data Person = Person { name :: String, age :: Int } deriving Typeable

你也可以编写自己的实例,但真的没有人有时间.显然你不能 – 看到评论

现在可以使用typeOf来获取类型的运行时表示.我们可以查询有关类型构造函数(缩写为TyCon)及其类型参数的信息:

-- (undefined :: Person) stands for "some value of type Person".
-- If you have a real Person you can use that too.
-- typeOf does not use the value, only the type
-- (which is known at compile-time; typeOf is dispatched using the normal instance selection rules)
ghci> typeOf (undefined :: Person)
Person
ghci> tyConName $typeRepTyCon $typeOf (undefined :: Person)
"Person"
ghci> tyConModule $typeRepTyCon $typeOf (undefined :: Person)
"Main"

Data.Typeable还提供了一种类型安全的转换操作,它允许您在值的运行时类型上分支,有点像C#作为运算符.

f :: Typeable a => a -> String
f x = case (cast x :: Maybe Int) of
           Just i -> "I can treat i as an int in this branch " ++ show (i * i)
           Nothing -> case (cast x :: Maybe Bool) of
                           Just b -> "I can treat b as a bool in this branch " ++ if b then "yes" else "no"
                           Nothing -> "x was of some type other than Int or Bool"

ghci> f True
"I can treat b as a bool in this branch yes"
ghci> f (3 :: Int)
"I can treat i as an int in this branch 9"

顺便提一句,写f的更好的方法是使用一个GADT枚举你希望你的函数调用的一组类型.这使我们失去了可能(f永远不会失败!),更好地记录我们的假设,并在需要更改f的可接受参数类型时给出编译时反馈. (如果你喜欢,你可以写一个类允许隐含).

data Admissible a where
    AdInt :: Admissible Int
    AdBool :: Admissible Bool
f :: Admissible a -> a -> String
f AdInt i = "I can treat i as an int in this branch " ++ show (i * i)
f AdBool b = "I can treat b as a bool in this branch " ++ if b then "yes" else "no"

实际上,我可能不会做任何一个 – 我只是将f放在一个类中并为Int和Bool定义实例.

如果您想要关于类型定义右侧的运行时信息,则需要使用名为命名的Data.Data,它定义了一个名为Data的可分类的子类.** GHC可以为您导出数据,同样延期:

{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
import Data.Data
data Person = Person { name :: String, age :: Int } deriving (Typeable, Data)

现在我们可以获取类型的值的运行时表示,而不仅仅是类型本身:

ghci> dataTypeOf (undefined :: Person)
DataType {tycon = "Main.Person", datarep = AlgRep [Person]}
ghci> dataTypeConstrs $dataTypeOf (undefined :: Person)
[Person]  -- Person only defines one constructor, called Person
ghci> constrFields $head $dataTypeConstrs $dataTypeOf (undefined :: Person)
["name","age"]

Data.Data是通用编程的API;如果你听到有人在说“Scrap Your Boilerplate”,那么这个(连同Data.Generics,建立在Data.Data上)就是他们的意思.例如,您可以编写一个函数,它可以使用类型字段上的反射将记录类型转换为JSON.

toJSON :: Data a => a -> String
-- Implementation omitted because it is boring.
-- But you only have to write the boring code once,
-- and it'll be able to serialise any instance of `Data`.
-- It's a good exercise to try to write this function yourself!

*在最近的GHC版本中,这个API有所改变.请咨询文档.

是的,该类的完全限定名称是Data.Data.Data.

翻译自:https://stackoverflow.com/questions/28243383/how-can-i-read-the-metadata-of-a-type-at-runtime

转载注明原文:haskell – 如何在运行时读取类型的元数据?