Идрис: доказательство о конкатенации векторов - PullRequest
2 голосов
/ 20 апреля 2020

Предположим, у меня есть следующий исходный код idris:

module Source

import Data.Vect

--in order to avoid compiler confusion between Prelude.List.(++), Prelude.String.(++) and Data.Vect.(++)
infixl 0 +++
(+++) : Vect n a -> Vect m a -> Vect (n+m) a
v +++ w = v ++ w
--NB: further down in the question I'll assume this definition isn't needed because the compiler
--    will have enough context to disambiguate between these and figure out that Data.Vect.(++)
--    is the "correct" one to use.

lemma : reverse (n :: ns) +++ (n :: ns) = reverse ns +++ (n :: n :: ns)
lemma {ns = []}       = Refl
lemma {ns = n' :: ns} = ?lemma_rhs

Как показано, базовый вариант для lemma тривиально Refl. Но я не могу найти способ доказать индуктивный случай: ответ "просто" выплевывает следующее

*source> :t lemma_rhs
  phTy : Type
  n1 : phTy
  len : Nat
  ns : Vect len phTy
  n : phTy
-----------------------------------------
lemma_rhs : Data.Vect.reverse, go phTy
                                  (S (S len))
                                  (n :: n1 :: ns)
                                  [n1, n]
                                  ns ++
            n :: n1 :: ns =
            Data.Vect.reverse, go phTy (S len) (n1 :: ns) [n1] ns ++
            n :: n :: n1 :: ns

Я понимаю, что phTy означает "фантомный тип", неявный тип из векторов, которые я рассматриваю. Я также понимаю, что go - это имя функции, определенной в предложении where для определения библиотечной функции reverse.

Вопрос

Как я могу продолжить доказательство? Оправдана ли моя индуктивная стратегия? Есть ли лучший?

Context

Это появилось в одном из моих игрушечных проектов, где я пытаюсь определить произвольные тензоры; в частности, это, по-видимому, необходимо для определения «полного сокращения индекса». Я немного подробнее остановлюсь на этом:

Я определяю тензоры таким образом, что примерно эквивалентно

data Tensor : (rank : Nat) -> (shape : Vector rank Nat) -> Type where
  Scalar : a -> Tensor Z [] a
  Vector : Vect n (Tensor rank shape a) -> Tensor (S rank) (n :: shape) a

, приукрашивающему остальную часть исходного кода (поскольку это не имеет отношения и это довольно долго и неинтересно на данный момент), я смог определить следующие функции

contractIndex : Num a =>
                Tensor (r1 + (2 + r2)) (s1 ++ (n :: n :: s2)) a ->
                Tensor (r1 + r2) (s1 ++ s2) a
tensorProduct : Num a =>
                Tensor r1 s1 a ->
                Tensor r2 s2 a ->
                Tensor (r1 + r2) (s1 ++ s2) a
contractProduct : Num a =>
                  Tensor (S r1) s1 a ->
                  Tensor (S r2) ((last s1) :: s2) a ->
                  Tensor (r1 + r2) ((take r1 s1) ++ s2) a

, и я работаю над этой другой

fullIndexContraction : Num a =>
                       Tensor r (reverse ns) a ->
                       Tensor r ns a ->
                       Tensor 0 [] a
fullIndexContraction {r = Z}   {ns = []}      t s = t * s
fullIndexContraction {r = S r} {ns = n :: ns} t s = ?rhs

, которая должна " итерируйте contractProduct как можно больше (то есть r раз) "; эквивалентно, можно было бы определить его как tensorProduct, составленный из максимально возможного числа contractIndex (опять же, эта сумма должна быть r).

Я включаю все это, потому что, возможно, это проще просто решить эту проблему, не доказав lemma выше: если бы это было так, я также был бы полностью удовлетворен. Я просто подумал, что с «более короткой» версией, приведенной выше, легче разобраться, так как я уверен, что сам смогу найти недостающие фрагменты.

Версия idris, которую я использую: 1.3.2-git:PRE (это то, что говорит repl при вызове из командной строки).

Edit : ответ xa sh охватывает почти все, и я смог написать следующее functions

nreverse_id : (k : Nat) -> nreverse k = k
contractAllIndices : Num a =>
                     Tensor (nreverse k + k) (reverse ns ++ ns) a ->
                     Tensor Z [] a
contractAllProduct : Num a =>
                     Tensor (nreverse k) (reverse ns) a ->
                     Tensor k ns a ->
                     Tensor Z []

Я также написал «причудливую» версию reverse, назовем ее fancy_reverse, которая автоматически переписывает nreverse k = k в своем результате. Поэтому я попытался написать функцию, которая не имеет nreverse в своей подписи, что-то вроде

fancy_reverse : Vect n a -> Vect n a
fancy_reverse {n} xs =
  rewrite sym $ nreverse_id n in
  reverse xs

contract : Num a =>
           {auto eql : fancy_reverse ns1 = ns2} ->
           Tensor k ns1 a ->
           Tensor k ns2 a ->
           Tensor Z [] a
contract {eql} {k} {ns1} {ns2} t s =
  flip contractAllProduct s $
  rewrite sym $ nreverse_id k in
  ?rhs

сейчас, предполагаемый тип для rhs равен Tensor (nreverse k) (reverse ns2), и я имею в виду переписать Правило для k = nreverse k, но я не могу понять, как переписать неявное eql доказательство для проверки этого типа: я делаю что-то не так?

1 Ответ

1 голос
/ 21 апреля 2020

Прелюдия Data.Vect.reverse трудно рассуждать, потому что AFAIK вспомогательная функция go не будет разрешена в проверке типов. Обычный подход - определить себя проще reverse, которому не нужно rewrite на уровне типа. Как здесь, например, :

%hide Data.Vect.reverse

nreverse : Nat -> Nat
nreverse Z = Z
nreverse (S n) = nreverse n + 1

reverse : Vect n a -> Vect (nreverse n) a
reverse [] = []
reverse (x :: xs) = reverse xs ++ [x]

lemma : {xs : Vect n a} -> reverse (x :: xs) = reverse xs ++ [x]
lemma = Refl

Как видите, это определение достаточно простое, что эта эквивалентная лемма может быть решена без дальнейшей работы. Таким образом, вы, вероятно, можете просто сопоставить reverse ns в fullIndexContraction, как в этом примере:

data Foo : Vect n Nat -> Type where
    MkFoo : (x : Vect n Nat) -> Foo x

foo : Foo a -> Foo (reverse a) -> Nat
foo (MkFoo [])      (MkFoo []) = Z
foo (MkFoo $ x::xs) (MkFoo $ reverse xs ++ [x]) =
    x + foo (MkFoo xs) (MkFoo $ reverse xs)

К вашему комментарию: во-первых, иногда необходимо использовать len = nreverse len, но если вы имели rewrite на уровне типа (через обычные n + 1 = 1 + n махинации) у вас была та же проблема (если даже не с более сложными доказательствами, но это только предположение.)

vectAppendAssociative на самом деле достаточно :

lemma2 : Main.reverse (n :: ns1) ++ ns2 = Main.reverse ns1 ++ (n :: ns2)
lemma2 {n} {ns1} {ns2} = sym $ vectAppendAssociative (reverse ns1) [n] ns2

...