Agda 2.3.2.1无法看到以下函数终止:
open import Data.Nat
open import Data.List
open import Relation.Nullary
merge : List ℕ → List ℕ → List ℕ
merge (x ∷ xs) (y ∷ ys) with x ≤? y
... | yes p = x ∷ merge xs (y ∷ ys)
... | _ = y ∷ merge (x ∷ xs) ys
merge xs ys = xs ++ ys
Agda wiki说,如果递归调用的参数按字典顺序减少,则终止检查器就可以了。基于此,似乎这个功能也应该通过。那我在这里错过了什么?此外,在以前版本的Agda中可能还可以吗?我在互联网上看过类似的代码,没有人提到终止问题。
答案 0 :(得分:6)
我不能告诉你为什么会发生这种情况,但我可以告诉你如何治愈这些症状。在开始之前:这是带有终止检查程序的known problem。如果您精通Haskell,可以查看source。
一种可能的解决方案是将函数拆分为两个:第一个用于第一个参数变小的情况,第二个用于第二个参数:
mutual
merge : List ℕ → List ℕ → List ℕ
merge (x ∷ xs) (y ∷ ys) with x ≤? y
... | yes _ = x ∷ merge xs (y ∷ ys)
... | no _ = y ∷ merge′ x xs ys
merge xs ys = xs ++ ys
merge′ : ℕ → List ℕ → List ℕ → List ℕ
merge′ x xs (y ∷ ys) with x ≤? y
... | yes _ = x ∷ merge xs (y ∷ ys)
... | no _ = y ∷ merge′ x xs ys
merge′ x xs [] = x ∷ xs
所以,第一个函数会减去xs
,一旦我们必须切断ys
,我们就会切换到第二个函数,反之亦然。
另一个(也许是令人惊讶的)选项,也是在问题报告中提到的,是通过with
引入递归的结果:
merge : List ℕ → List ℕ → List ℕ
merge (x ∷ xs) (y ∷ ys) with x ≤? y | merge xs (y ∷ ys) | merge (x ∷ xs) ys
... | yes _ | r | _ = x ∷ r
... | no _ | _ | r = y ∷ r
merge xs ys = xs ++ ys
最后,我们可以在Vec
转发器上执行递归,然后转换回List
:
open import Data.Vec as V
using (Vec; []; _∷_)
merge : List ℕ → List ℕ → List ℕ
merge xs ys = V.toList (go (V.fromList xs) (V.fromList ys))
where
go : ∀ {n m} → Vec ℕ n → Vec ℕ m → Vec ℕ (n + m)
go {suc n} {suc m} (x ∷ xs) (y ∷ ys) with x ≤? y
... | yes _ = x ∷ go xs (y ∷ ys)
... | no _ rewrite lem n m = y ∷ go (x ∷ xs) ys
go xs ys = xs V.++ ys
但是,这里我们需要一个简单的引理:
open import Relation.Binary.PropositionalEquality
lem : ∀ n m → n + suc m ≡ suc (n + m)
lem zero m = refl
lem (suc n) m rewrite lem n m = refl
我们也可以go
直接返回List
并完全避免引理:
merge : List ℕ → List ℕ → List ℕ
merge xs ys = go (V.fromList xs) (V.fromList ys)
where
go : ∀ {n m} → Vec ℕ n → Vec ℕ m → List ℕ
go (x ∷ xs) (y ∷ ys) with x ≤? y
... | yes _ = x ∷ go xs (y ∷ ys)
... | no _ = y ∷ go (x ∷ xs) ys
go xs ys = V.toList xs ++ V.toList ys
第一个技巧(即将功能分成几个相互递归的功能)实际上是非常好的记忆。由于终止检查器没有查看您使用的其他函数的定义,因此它拒绝了大量完美的程序,请考虑:
data Rose {a} (A : Set a) : Set a where
[] : Rose A
node : A → List (Rose A) → Rose A
现在,我们想要实施mapRose
:
mapRose : ∀ {a b} {A : Set a} {B : Set b} →
(A → B) → Rose A → Rose B
mapRose f [] = []
mapRose f (node t ts) = node (f t) (map (mapRose f) ts)
然而,终止检查器不会查看map
内部,看它是否对元素没有做任何简单的事情,只是拒绝这个定义。我们必须内联map
的定义并编写一对相互递归的函数:
mutual
mapRose : ∀ {a b} {A : Set a} {B : Set b} →
(A → B) → Rose A → Rose B
mapRose f [] = []
mapRose f (node t ts) = node (f t) (mapRose′ f ts)
mapRose′ : ∀ {a b} {A : Set a} {B : Set b} →
(A → B) → List (Rose A) → List (Rose B)
mapRose′ f [] = []
mapRose′ f (t ∷ ts) = mapRose f t ∷ mapRose′ f ts
通常,您可以隐藏where
声明中的大部分内容:
mapRose : ∀ {a b} {A : Set a} {B : Set b} →
(A → B) → Rose A → Rose B
mapRose {A = A} {B = B} f = go
where
go : Rose A → Rose B
go-list : List (Rose A) → List (Rose B)
go [] = []
go (node t ts) = node (f t) (go-list ts)
go-list [] = []
go-list (t ∷ ts) = go t ∷ go-list ts
注意:在较新版本的Agda中,可以使用在定义两个函数的签名之前代替mutual
而不是merge : List A → List A → List A
merge [] ys = ys
merge xs [] = xs
merge (x ∷ xs) (y ∷ ys) with x ≤ y
merge (x ∷ xs) (y ∷ ys) | false = y ∷ merge (x ∷ xs) ys
merge (x ∷ xs) (y ∷ ys) | true = x ∷ merge xs (y ∷ ys)
。
更新:Agda的开发版本获得了终止检查器的更新,我将让提交消息和发布说明自行说明:
- 可以处理任意终止深度的调用图完成的修订版。 这个算法已经在MiniAgda中待了一段时间, 等待美好的一天。它现在在这里! 选项 - 终止深度现在可以退休。
从发行说明:
由'with'定义的函数的终止检查已得到改进。
以前需要的案例 - 终止深度(现已过时!) 通过终止检查员(由于使用'带')不再是 需要国旗。例如
merge-aux x y xs ys false = y ∷ merge (x ∷ xs) ys merge-aux x y xs ys true = x ∷ merge xs (y ∷ ys)
此前未能终止检查,因为'with' 扩展为辅助函数merge-aux:
bad : Nat → Nat bad n with n ... | zero = zero ... | suc m = bad m
这个函数调用合并其中一个的大小 争论正在增加。使这通过终止检查器 现在在检查之前概述merge-aux的定义 有效终止检查原始源程序。
由于这种转换在变量no上执行'with' 更长时间保留终止。例如,这不是 终止检查:
{{1}}
事实上,您的原始功能现在通过了终止检查!