Vector Idris:关于向量串联的证明

Vector Idris:关于向量串联的证明,vector,tensor,proof,idris,Vector,Tensor,Proof,Idris,假设我有以下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 d

假设我有以下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
如图所示,
引理
的基本情况是平凡的
Refl
。但我似乎找不到一种方法来证明归纳的情况:repl“只是”吐出以下内容

*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

问题

我怎样才能继续证明?我的归纳策略正确吗?有更好的吗

上下文

在我的一个玩具项目中,我尝试定义任意张量;具体而言,定义“完全指数收缩”似乎需要这样做。我会详细说明一下:

我定义张量的方式大致相当于

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

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 []
我正在做另一个

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
(同样,该金额应为
r

我把所有这些都包括在内,因为在不证明上面的
引理的情况下解决这个问题可能更容易:如果是这样的话,我也会完全满意。我只是觉得上面的“较短”版本可能更容易处理,因为我很确定我能自己找出缺失的部分

我使用的idris版本是
1.3.2-git:PRE
(从命令行调用repl时会这样说)

编辑:xash的答案几乎涵盖了所有内容,我能够编写以下函数

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

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\u 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)(反向ns2)
,我在范围内有一个
k=nreverse k
的重写规则,但是我似乎不知道如何重写隐式的
eql
证明来进行类型检查:我做错了什么吗?

前奏曲
Data.Vect.reverse
很难解释,因为在类型检查器中不会解析
go
helper函数。通常的方法是将自己定义为一个更简单的
反向
,而不需要在类型级别上重写

正如你们所看到的,这个定义很直接,这个等价引理不需要进一步的工作就可以解决。因此,您可能只需在
fullIndexContraction
中的
reverse ns
上进行匹配,如本例所示:

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
,但是如果您在类型级别(通过通常的
n+1=1+n
骗局)上有
rewrite
,您也会遇到同样的问题(即使没有更复杂的证明,但这只是一个猜测)

vectapendassociative
实际上已经足够了:

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


我试图在我的项目中实现您的答案,但有些事情让我感到困扰。第一个是,我必须证明(并一遍又一遍地重写)
nreverse k=k
;这并不困难,但可能会使代码更不可读。或者,我可以通过在其定义中重写这样的证明来改变反转的类型:这可能会在这方面节省我的时间。第二,也是更重要的,是它不像我期望的那样容易证明
(反转n::ns1)++ns2=(反转ns1)++(n::ns2)
:我可能遗漏了一些东西,我很高兴听到你对这件事的看法。(我尝试使用
++
是关联的这一事实,但显然这对编译器来说还不够)更新了我的答案,因为vectasociate部分在这里不适合提交。非常感谢您的解释!我仍然有一个小问题,它将不适合在评论。为了完整起见,我将对问题进行编辑,对其进行详细说明。
eql
问题正是“
rewrite
在类型级别上”。有时使用显式的
replace
至少有助于了解发生了什么,但通常您希望使用简单的函数,如
reverse
,或者更简单但更详细的使用数据类型证明
IsReversed ns1 ns2
。顺便说一句,如果您只是对键入
(nrevert k)(revert xs)
感到厌烦,请记住您可以使用辅助类型,例如:
翻转:(k:Nat)->Vect k Nat->Type->Type;翻转的kxs a=张量(nreverse k)(反向xs)a