Idris:关于向量连接的证明
Idris: proof about concatenation of vectors
假设我有以下 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
。但我似乎无法找到证明归纳案例的方法:repl "just" 吐出以下内容
*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
代表 "phantom type",我正在考虑的向量的隐式类型。我也明白go
是库函数reverse
定义的where
子句中定义的函数名。
问题
How can I continue the proof? Is my inductive strategy sound? Is there a better one?
上下文
这出现在我的一个玩具项目中,我试图在其中定义任意张量;具体来说,这似乎是定义 "full index contraction" 所必需的。我会详细说明一下:
我定义张量的方式大致等同于
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
应该"iterate contractProduct
as much as possible (that is, r
times)";等效地,可以将其定义为 tensorProduct
由尽可能多的 contractIndex
组成(同样,该数量应该是 r
)。
我把所有这些都包括在内是因为不证明上面的 lemma
就可以更容易地解决这个问题:如果是这样的话,我也会完全满意。我只是觉得上面的 "shorter" 版本可能更容易处理,因为我很确定我自己能够找出缺失的部分。
我使用的 idris 版本是 1.3.2-git:PRE
(当从命令行调用时 repl 是这么说的)。
编辑: xash 的答案几乎涵盖了所有内容,我能够编写以下函数
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" 版本,我们称它为 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
证明以进行此类型检查:我做错了什么吗?
序曲 Data.Vect.reverse
很难推理,因为据我所知 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
如您所见,这个定义足够简单,无需进一步工作即可解决这个等价引理。因此,您可能只匹配 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
,但是如果您在类型级别上有 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
假设我有以下 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
。但我似乎无法找到证明归纳案例的方法:repl "just" 吐出以下内容
*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
代表 "phantom type",我正在考虑的向量的隐式类型。我也明白go
是库函数reverse
定义的where
子句中定义的函数名。
问题
How can I continue the proof? Is my inductive strategy sound? Is there a better one?
上下文
这出现在我的一个玩具项目中,我试图在其中定义任意张量;具体来说,这似乎是定义 "full index contraction" 所必需的。我会详细说明一下:
我定义张量的方式大致等同于
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
应该"iterate contractProduct
as much as possible (that is, r
times)";等效地,可以将其定义为 tensorProduct
由尽可能多的 contractIndex
组成(同样,该数量应该是 r
)。
我把所有这些都包括在内是因为不证明上面的 lemma
就可以更容易地解决这个问题:如果是这样的话,我也会完全满意。我只是觉得上面的 "shorter" 版本可能更容易处理,因为我很确定我自己能够找出缺失的部分。
我使用的 idris 版本是 1.3.2-git:PRE
(当从命令行调用时 repl 是这么说的)。
编辑: xash 的答案几乎涵盖了所有内容,我能够编写以下函数
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" 版本,我们称它为 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
证明以进行此类型检查:我做错了什么吗?
序曲 Data.Vect.reverse
很难推理,因为据我所知 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
如您所见,这个定义足够简单,无需进一步工作即可解决这个等价引理。因此,您可能只匹配 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
,但是如果您在类型级别上有 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