我想编写一个打印出Haskell类型的元数据的程序。虽然我知道这不是有效的代码,但这个想法是这样的:
data Person = Person { name :: String, age :: Int }
metadata :: Type -> String
metadata t = ???
metadata Person -- returns "Person (name,age)"
重要的限制是我没有Person
的实例,只有类型。
我已经开始研究泛型&可键入/数据,但没有实例我不确定他们会做我需要的。有人能指出我正确的方向吗?
答案 0 :(得分:22)
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#的as
运算符。
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枚举您希望调用函数的类型集。这使我们失去了Maybe
(f
永远不会失败!),更好地记录我们的假设,并在我们需要更改可接受的参数类型集时提供编译时反馈f
。 (如果您愿意,可以编写一个隐式Admissible
的类。)
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
,它定义了Typeable
的子类Data
}。** GHC也可以为您推导Data
,具有相同的扩展名:
{-# 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.Data
建立的Data.Generics
)就是他们的意思。例如,您可以编写一个函数,使用类型字段的反射将记录类型转换为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
。