终止检查列表合并

时间:2013-07-28 17:17:01

标签: agda

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中可能还可以吗?我在互联网上看过类似的代码,没有人提到终止问题。

1 个答案:

答案 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}}
  •   

事实上,您的原始功能现在通过了终止检查!