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

时间:2015-04-27 18:22:44

标签: haskell types

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

succ 0 = 1
succ 1 = 2
...
succ n = 0

n在编译时未知。我可以这样做:

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

wrapInt :: Int -> Int -> WrapInt
wrapInt boundary value = WrapInt value boundary

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

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

2 个答案:

答案 0 :(得分:6)

reflection包允许您在运行时生成类型类的新“本地”实例。

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

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

import Data.Reflection
import Data.Proxy

class Wrappy w where
   succWrappy :: w -> w

我们定义了带有幻像类型参数的新类型:

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包的内部机制完成的。

因此,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))

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

this中的reflection GitHub repo回答中查看更多示例。

答案 1 :(得分:4)

这不是不可能的 - 只是非常难看。我们需要自然数

data Nat = Z | S Nat

和有界自然数

data Bounded (n :: Nat) where
    BZ :: Bounded n
    BS :: Bounded n -> Bounded (S n)

然后你的功能应该是

succ :: Bounded n -> Bounded n
succ bn = fromMaybe BZ $ go bn where
    go :: Bounded n -> Maybe (Bounded n)
    go = ...

go我们需要

  1. BZ映射到Nothing,如果nZ(即Bounded达到最大值并且已溢出)
  2. BZ映射到Just (BS BZ),如果n不是Z(即如果Bounded没有达到最大值)。
  3. 针对go案例递归致电BS
  4. 然而问题是无法在值级别获得n。 Haskell不是那种依赖。通常的黑客是使用singletons。手动编写

    data Natty (n :: Nat) where
        Zy :: Natty Z
        Sy :: Natty n -> Natty (S n)
    
    class NATTY (n :: Nat) where
        natty :: Natty n
    
    instance NATTY Z where
        natty = Zy
    
    instance NATTY n => NATTY (S n) where
        natty = Sy natty
    

    现在,我们可以在n的{​​{1}}中获得Bounded n的价值级代表:

    go

    succ :: NATTY n => Bounded n -> Bounded n succ bn = fromMaybe BZ $ go natty bn where go :: Natty n -> Bounded n -> Maybe (Bounded n) go Zy BZ = Nothing go (Sy ny) BZ = Just (BS BZ) go (Sy ny) (BS bn) = BS <$> go ny bn 类型类用于自动推断该值。

    一些测试:

    NATTY

    code

    使用singletons库,我们可以将instance Eq (Bounded n) where BZ == BZ = True BS bn == BS bm = bn == bm _ == _ = False zero :: Bounded (S (S Z)) zero = BZ one :: Bounded (S (S Z)) one = BS BZ two :: Bounded (S (S Z)) two = BS (BS BZ) main = do print $ succ zero == zero -- False print $ succ zero == one -- True print $ succ one == two -- True print $ succ two == zero -- True 定义为

    succ

    至于将运行时内容提升到类型级别,有两种方法:CPSexistential types