在运行时动态生成Haskell类型?

Sve*_*mir 5 haskell types

可以在运行时从给定模板定义Haskell类型吗?这就是我的意思.假设我需要一个限制在某个范围内的整数类型(在编译时精确地未知).我还想要一个功能:

succ 0 = 1
succ 1 = 2
...
succ n = 0
Run Code Online (Sandbox Code Playgroud)

n在编译时不知道.我可以这样做:

data WrapInt = WrapInt {
        value       :: Int,
        boundary    :: Int
}

wrapInt :: Int -> Int -> WrapInt
wrapInt boundary value = WrapInt value boundary
Run Code Online (Sandbox Code Playgroud)

现在我想要的是保留wrapInt函数,但避免将边界存储为WrapInt类型中的值.相反,我希望它以某种方式存储在类型定义中,这当然意味着必须在运行时动态定义类型.

是否有可能在Haskell中实现这一目标?

dan*_*iaz 6

reflection包允许您在运行时生成类型类的新"本地"实例.

例如,假设我们有以下类型的值可以"环绕":

{-# LANGUAGE Rank2Types, FlexibleContexts, UndecidableInstances #-}

import Data.Reflection
import Data.Proxy

class Wrappy w where
   succWrappy :: w -> w
Run Code Online (Sandbox Code Playgroud)

我们定义这个带有幻像类型参数的newtype:

data WrapInt s = WrapInt { getValue :: Int } deriving Show
Run Code Online (Sandbox Code Playgroud)

使它成为一个实例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)
Run Code Online (Sandbox Code Playgroud)

有趣的部分是Reifies s Int约束.这意味着:"幻像类型s表示Int类型级别的类型值".用户从不定义实例Reifies,这是由reflection包的内部机制完成的.

所以,Reifies s Int => Wrappy (WrapInt s)意思是:"只要s代表一个类型的值Int,我们就可以创建WrapInt s一个实例Wrappy".

reflect函数采用与幻像类型匹配的代理值,并返回实际Int值,该值在实现Wrappy实例时使用.

要实际为幻像类型"赋值",我们使用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))
Run Code Online (Sandbox Code Playgroud)

请注意,签名reify禁止幻像类型转义回调,这就是我们必须打开结果的原因getValue.

反思GitHub repo中查看答案中的更多示例.