(注意:我只对这段代码进行了类型检查,而没有真正运行它。)
方法1
实际上,您可以通过将证明存储在GADTs中来操纵证明。为使此方法有效,您需要打开ScopedTypeVariables
。
data Proof n where
NilProof :: Proof Ze
ConsProof :: (n + Su Ze) ~ Su n => Proof n -> Proof (Su n)
class PlusOneIsSucc n where proof :: Proof n
instance PlusOneIsSucc Ze where proof = NilProof
instance PlusOneIsSucc n => PlusOneIsSucc (Su n) where
proof = case proof :: Proof n of
NilProof -> ConsProof proof
ConsProof _ -> ConsProof proof
rev :: PlusOneIsSucc n => Vec a n -> Vec a n
rev = go proof where
go :: Proof n -> Vec a n -> Vec a n
go NilProof Nil = Nil
go (ConsProof p) (Cons x xs) = go p xs `append` Cons x Nil
实际上,Proof
类型的有趣动机可能是,我最初只有
data Proof n where Proof :: (n + Su Ze) ~ Su n => Proof n
但是,这并没有起作用:GHC正确地指出,仅仅因为我们知道
(Su n)+1 = Su (Su n)
并不意味着我们知道
n+1 = Su n
,这正是我们需要知道的,以便在
Cons
情况下进行递归调用
rev
。因此,我不得不扩展
Proof
的含义,包括自然数等式的所有证明,直到包括
n
--本质上与从归纳到强归纳时进行加强处理类似。
方法2
经过一番思考,我意识到其实这个类有点多余;这使得这种方法特别好,因为它不需要任何额外的扩展(甚至不需要
ScopedTypeVariables
),也不会对
Vec
类型引入任何额外的约束。
data Proof n where
NilProof :: Proof Ze
ConsProof :: (n + Su Ze) ~ Su n => Proof n -> Proof (Su n)
proofFor :: Vec a n -> Proof n
proofFor Nil = NilProof
proofFor (Cons x xs) = let rec = proofFor xs in case rec of
NilProof -> ConsProof rec
ConsProof _ -> ConsProof rec
rev :: Vec a n -> Vec a n
rev xs = go (proofFor xs) xs where
go :: Proof n -> Vec a n -> Vec a n
go NilProof Nil = Nil
go (ConsProof p) (Cons x xs) = go p xs `append` Cons x Nil
方法3
或者,如果你稍微修改rev
的实现,将最后一个元素连接到列表初始段的逆序上,那么代码看起来会更加简单明了。(这种方法也不需要额外的扩展)
class Rev n where
initLast :: Vec a (Su n) -> (a, Vec a n)
rev :: Vec a n -> Vec a n
instance Rev Ze where
initLast (Cons x xs) = (x, xs)
rev x = x
instance Rev n => Rev (Su n) where
initLast (Cons x xs) = case initLast xs of
(x', xs') -> (x', Cons x xs')
rev as = case initLast as of
(a, as') -> Cons a (rev as')
方法四
与方法三类似,但再次注意到类型类是不必要的。
initLast :: Vec a (Su n) -> (a, Vec a n)
initLast (Cons x xs) = case xs of
Nil -> (x, Nil)
Cons {} -> case initLast xs of
(x', xs') -> (x', Cons x xs')
rev :: Vec a n -> Vec a n
rev Nil = Nil
rev xs@(Cons {}) = case initLast xs of
(x, xs') -> Cons x (rev xs')
+
替换为前缀Add
,并且需要加入{-# LANGUAGE GADTs, DataKinds, TypeFamilies #-}
。 - nponeccop