Ответ 1
(Примечание. У меня есть только проверенный тип (и фактически не выполняемый) любого из этого кода.)
Подход 1
Собственно, вы можете манипулировать доказательствами, сохраняя их в GADT. Чтобы этот подход работал, вам нужно включить 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
, что нам нужно знать, чтобы сделать рекурсивный вызов rev
в случай Cons
. Поэтому мне пришлось расширить значение a 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')
Подход 4
Подобно подходу 3, но опять же, заметив, что классы типов не нужны.
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 [email protected](Cons {}) = case initLast xs of
(x, xs') -> Cons x (rev xs')