如何在运行时读取类型的元数据? [英] How can I read the metadata of a type at runtime?

查看:81
本文介绍了如何在运行时读取类型的元数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想写一个程序,打印出一些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类工作,该类在

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放在一个类中,并为IntBool定义实例.

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.

如果要获取有关类型定义右侧的运行时信息,则需要使用名称有趣的

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.

这篇关于如何在运行时读取类型的元数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆