我正在试验Haskell的类型系统,并想编写一个类型安全的加法函数。此函数应接受两个表示数字的单例见证人,并返回该数字的单例见证人,其类型带有证明它确实是参数之和的证明。这是代码:
{-# language TypeFamilies, KindSignatures, DataKinds, PolyKinds, UndecidableInstances, GADTs #-}
data Nat = Zero | Succ Nat deriving Show
type family Add (m :: Nat) (n :: Nat) :: Nat where
Add Zero n = n
Add (Succ m) n = Add m (Succ n)
data SNat :: Nat -> * where
Zy :: SNat Zero
Suc :: SNat m -> SNat (Succ m)
data Bounded' m = B m
sum' :: Bounded' (SNat m) -> Bounded' (SNat n) -> Bounded' (SNat (Add m n))
sum' (B m) (B n) = B $ case (m, n) of
(Zy,x) -> x
(Suc x, y) -> let B z = sum' (B x) (B y) in Suc z
这是错误:
• Could not deduce: Add m1 ('Succ n) ~ 'Succ (Add m1 n)
from the context: m ~ 'Succ m1
bound by a pattern with constructor:
Suc :: forall (m :: Nat). SNat m -> SNat ('Succ m),
in a case alternative
at main.hs:17:22-26
Expected type: SNat (Add m n)
Actual type: SNat ('Succ (Add m1 n))
• In the expression: Suc z
In the expression: let B z = sum' (B x) (B y) in Suc z
In a case alternative:
(Suc x, y) -> let B z = sum' (B x) (B y) in Suc z
我了解错误消息。当它得知m〜Succ k(在第二种情况下匹配)并且有替代方法时,如何为GHC提供必要的证明,即表达式Suc z
中的Add m n = Succ(Add k n)。谢谢。
答案 0 :(得分:9)
您对加法的定义不是传统的定义。
type family Add (m :: Nat) (n :: Nat) :: Nat where
Add Zero n = n
Add (Succ m) n = Add m (Succ n)
这是“尾递归”添加。似乎应该有一种方法可以使用这种加法形式来证明您的属性,但是我无法弄清楚。在此之前,与标准种类相比,在类型/属性级别上进行尾递归往往要困难得多:
type family Add (m :: Nat) (n :: Nat) :: Nat where
Add Zero n = n
Add (Succ m) n = Succ (Add m n)
后面的加法定义使您的sum'
通过而毫无说服力。
编辑实际上,一旦我看对了,这很容易。这就是我得到的(导入Data.Type.Equality
并启用LANGUAGE TypeOperators
):
propSucc2 :: SNat m -> SNat n -> Add m (Succ n) :~: Succ (Add m n)
propSucc2 Zy _ = Refl
propSucc2 (Suc m) n = propSucc2 m (Suc n)
尾递归定义,尾递归证明。然后使用gcastWith
:
sum' (B m) (B n) = ...
(Suc x, y) -> gcastWith (propSucc2 x y)
(let B z = sum' (B x) (B y) in Suc z)
gcastWith
只需要一个:~:
相等性,并在第二个参数范围内将其提供给类型检查器。
顺便说一句,如果您在sum'
类型族的并行结构中定义Add
,则不需要任何引理。使事情遵循并行结构是使事情保持简单的好技术(这是依赖编程技术的一部分,因为并不总是那么容易做到):
sum' :: Bounded' (SNat m) -> Bounded' (SNat n) -> Bounded' (SNat (Add m n))
sum' (B Zy) (B n) = B n
sum' (B (Suc m)) (B n) = sum' (B m) (B (Suc n))