可以在运行时从给定模板定义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中实现这一目标?
答案 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
我们需要
BZ
映射到Nothing
,如果n
为Z
(即Bounded
达到最大值并且已溢出)BZ
映射到Just (BS BZ)
,如果n
不是Z
(即如果Bounded
没有达到最大值)。 go
案例递归致电BS
。然而问题是无法在值级别获得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
至于将运行时内容提升到类型级别,有两种方法:CPS和existential types。