我一直在尝试使用DataKinds
扩展名在Haskell中实现类型级自然。到目前为止,我的代码看起来像这样:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
-- the kind of natural numbers
data Nat = Zero | Succ Nat
-- type synonyms for the naturals
type N0 = Zero
type N1 = Succ N0
type N2 = Succ N1
-- ...
-- singleton natural type
data SNat (n :: Nat) where
SZero :: SNat Zero
SSucc :: SNat n -> SNat (Succ n)
-- type level lesser-than operator
type family (<) (x :: Nat) (y :: Nat) :: Bool where
x < Zero = False
Zero < y = True
(Succ x) < (Succ y) = x < y
-- type level addition operator
type family (+) (x :: Nat) (y :: Nat) :: Nat where
Zero + y = y
x + Zero = x
(Succ x) + y = Succ (x + y)
x + (Succ y) = Succ (x + y)
infixr 5 :::
-- type of vectors with a certain length
data Vector (n :: Nat) a where
Nil :: Vector N0 a
(:::) :: a -> Vector n a -> Vector (Succ n) a
-- indexing operator
(!) :: ((k < n) ~ True) => Vector n a -> SNat k -> a
(x ::: _) ! SZero = x
(_ ::: xs) ! (SSucc n) = xs ! n
此代码编译正常并按预期工作(在预期时也会出现类型错误)。
> (1 ::: 2 ::: 3 ::: Nil) ! (SSucc SZero)
2
> (1 ::: Nil) ! (SSucc SZero)
Couldn't match type 'False with 'True....
但是,如果我从上面改变其中一行:
(:::) :: a -> Vector n a -> Vector (Succ n) a
对此:
(:::) :: a -> Vector n a -> Vector (n + N1) a
该文件突然无法编译:
Could not deduce ((n2 < n1) ~ 'True)
from the context ((k < n) ~ 'True)
bound by the type signature for
(!) :: (k < n) ~ 'True => Vector n a -> SNat k -> a
at question.hs:41:8-52
or from (n ~ (n1 + N1))
bound by a pattern with constructor
::: :: forall a (n :: Nat). a -> Vector n a -> Vector (n + N1) a,
in an equation for ‘!’
at question.hs:43:2-9
or from (k ~ 'Succ n2)
bound by a pattern with constructor
SSucc :: forall (n :: Nat). SNat n -> SNat ('Succ n),
in an equation for ‘!’
at question.hs:43:15-21
Relevant bindings include
n :: SNat n2 (bound at question.hs:43:21)
xs :: Vector n1 a (bound at question.hs:43:8)
In the expression: xs ! n
In an equation for ‘!’: (_ ::: xs) ! (SSucc n) = xs ! n
为什么Haskell能够推导n < Succ n
而不是n < n + N1
?在这种情况下,如何使我的类型函数正常运行? (我不想使用unsafeCoerce
)。
答案 0 :(得分:7)
您可以通过缩小(+)
类型系列的定义来使用更改的类型签名进行编译:
-- type level addition operator
type family (+) (x :: Nat) (y :: Nat) :: Nat where
-- Zero + y = y
x + Zero = x
-- (Succ x) + y = Succ (x + y)
x + (Succ y) = Succ (x + y)
根据您的原始定义,(n + N1)
无法减少,因为GHC不知道它可以应用哪个方程式;第一个可能适用,具体取决于n
是Zero
。
使用缩减版本清除(在将N1
减少到其定义之后)只能应用x + Succ y
规则,因此GHC可以将您的新类型减少到原始版本之一。
通过第一个参数的案例分析来定义像(+)
这样的操作实际上更为正常,而不是像我在这里做的那样。这样可以使(N1 + n)
之类的工作正常而非(n + N1)
。但是我认为这只是一个惯例,而不是具有任何特定的优势。
通过同时拥有两组定义,您通常会遇到两个世界中最糟糕的定义。