问题描述
我想写一个程序,打印出一些Haskell类型的元数据.尽管我知道这不是有效的代码,但是这个主意是这样的:
I'd like to write a program that prints out some metadata of a Haskell type. Although I know this isn't valid code, the idea is something like:
data Person = Person { name :: String, age :: Int }
metadata :: Type -> String
metadata t = ???
metadata Person -- returns "Person (name,age)"
重要的限制是我没有Person
的实例,只有类型.
The important restriction being I don't have an instance of Person
, just the type.
我已经开始研究泛型&可键入/数据,但没有实例,我不确定它们会满足我的需求.谁能指出我正确的方向?
I've started looking into Generics & Typeable/Data, but without an instance I'm not sure they'll do what I need. Can anyone point me in the right direction?
推荐答案
在Haskell中,Reflection使用Typeable
类工作,该类在 Data.Typeable
并包括typeOf
*方法来获取值类型的运行时表示.
Reflection in Haskell works using the Typeable
class, which is defined in Data.Typeable
and includes the typeOf
* method to get a run-time representation of a value's type.
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
扩展名为您生成一个实例.
If you want Typeable
to work for your own types, you can have the compiler generate an instance for you with the DeriveDataTypeable
extension.
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
data Person = Person { name :: String, age :: Int } deriving Typeable
显然,您不能-查看注释
您现在可以使用typeOf
来获取您类型的运行时表示形式.我们可以查询有关类型构造函数(缩写为TyCon
)及其类型参数的信息:
You can now use typeOf
to grab a run-time representation of your type. We can query information about the type constructor (abbreviated to TyCon
) and its type arguments:
-- (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
还提供了 type-safe cast 操作,该操作使您可以分支到值的运行时类型上,类似于C#的as
运算符.
Data.Typeable
also provides a type-safe cast operation which allows you to branch on a value's runtime type, somewhat like C#'s as
operator.
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
隐式.)
Incidentally, a nicer way to write f
is to use a GADT enumerating the set of types you expect your function to be called with. This allows us to lose the Maybe
(f
can never fail!), does a better job of documenting our assumptions, and gives compile-time feedback when we need to change the set of admissible argument types for f
. (You can write a class to make Admissible
implicit if you like.)
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
定义实例.
In reality I probably wouldn't do either of these - I'd just stick f
in a class and define instances for Int
and Bool
.
如果要获取有关类型定义右侧的运行时信息,则需要使用名称有趣的 Data.Data
,它定义了名为Data
的Typeable
的子类.** GHC也可以为您派生Data
,相同的扩展名:
If you want run-time information about the right-hand side of a type definition, you need to use the entertainingly-named Data.Data
, which defines a subclass of Typeable
called Data
.** GHC can derive Data
for you too, with the same extension:
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
import Data.Data
data Person = Person { name :: String, age :: Int } deriving (Typeable, Data)
现在,我们可以获取类型的值的运行时表示,而不仅仅是类型本身:
Now we can grab a run-time representation of the values of a type, not just the type itself:
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;如果您曾经听到有人在谈论废话了",这(以及 Data.Generics
(建立在Data.Data
上)是他们的意思.例如,您可以编写一个函数,使用对类型字段的反射将记录类型转换为JSON.
Data.Data
is the API for generic programming; if you ever hear people talking about "Scrap Your Boilerplate", this (along with Data.Generics
, which builds on Data.Data
) is what they mean. For example, you can write a function which converts record types to JSON using reflection on the type's fields.
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
.
这篇关于如何在运行时读取类型的元数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!