在运行时动态生成Haskell类型? [英] Dynamically generate Haskell types at runtime?
问题描述
有人可以在运行时根据给定的模板定义Haskell类型吗?这就是我的意思.假设我需要一个限制在一定范围内的整数类型(在编译时未知).我还想要一个功能,
Can one define a Haskell type at runtime from a given template? Here's what I mean by this. Suppose I need an integer type that is restricted to some range (unknown precisely at compile time). I also want a feature that:
succ 0 = 1
succ 1 = 2
...
succ n = 0
n
在编译时未知.我可以做这样的事情:
n
being unknown at compile time. I could do something like this:
data WrapInt = WrapInt {
value :: Int,
boundary :: Int
}
wrapInt :: Int -> Int -> WrapInt
wrapInt boundary value = WrapInt value boundary
现在,我想要保留的是wrapInt
函数的原样,但要避免将边界作为值存储在WrapInt类型内.相反,我希望将它以某种方式存储在类型定义中,这当然意味着必须在运行时动态定义类型.
Now what I would like to have is to preserve the wrapInt
function as it is, but to avoid storing the boundary as a value inside WrapInt type. Instead I would like it to be stored somehow in type definition, which of course means that the type would have to be defined dynamically at runtime.
是否可以在Haskell中实现这一目标?
Is it possible to achieve this in Haskell?
推荐答案
The reflection
package lets you generate new "local" instances of a typeclass at runtime.
例如,假设我们具有以下可以包装"的值的类型类型:
For example, suppose we have the following typeclass of values that can "wrap around":
{-# LANGUAGE Rank2Types, FlexibleContexts, UndecidableInstances #-}
import Data.Reflection
import Data.Proxy
class Wrappy w where
succWrappy :: w -> w
我们定义了一个带有幻像类型参数的新类型:
We define this newtype that carries a phantom type parameter:
data WrapInt s = WrapInt { getValue :: Int } deriving Show
将其设为Wrappy
的实例:
instance Reifies s Int => Wrappy (WrapInt s) where
succWrappy w@(WrapInt i) =
let bound = reflect w
in
if i == bound
then WrapInt 0
else WrapInt (succ i)
有趣的部分是Reifies s Int
约束.这意味着:幻像类型s
在类型级别代表类型Int
的值".用户永远不会为Reifies
定义实例,这是由reflection
程序包的内部机制完成的.
The interesting part is the Reifies s Int
constraint. It means: "the phantom type s
represents a value of type Int
at the type level". Users never define an instance for Reifies
, this is done by the internal machinery of the reflection
package.
因此,Reifies s Int => Wrappy (WrapInt s)
的意思是:只要s
表示类型为Int
的值,我们就可以使WrapInt s
为Wrappy
的实例".
So, Reifies s Int => Wrappy (WrapInt s)
means: "whenever s
represent a value of type Int
, we can make WrapInt s
an instance of Wrappy
".
reflect
函数采用与幻像类型匹配的代理值,并返回实际的Int
值,该值在实现Wrappy
实例时使用.
The reflect
function takes a proxy value that matches the phantom type and brings back an actual Int
value, which is used when implementing the Wrappy
instance.
要实际将值分配"给幻像类型,我们使用修改:
To actually "assign" a value to the phantom type, we use reify:
-- Auxiliary function to convice the compiler that
-- the phantom type in WrapInt is the same as the one in the proxy
likeProxy :: Proxy s -> WrapInt s -> WrapInt s
likeProxy _ = id
main :: IO ()
main = print $ reify 5 $ \proxy ->
getValue $ succWrappy (likeProxy proxy (WrapInt 5))
请注意,reify
的签名禁止幻像类型转义回调,这就是为什么我们必须用getValue
解开结果的原因.
Notice that the signature of reify
forbids the phantom type from escaping the callback, that's why we must unwrap the result with getValue
.
在此答案中查看更多示例,在反射GitHub存储库中.
See more examples in this answer, on in the reflection GitHub repo.
这篇关于在运行时动态生成Haskell类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!