让 Agda 相信递归函数正在终止

Convincing Agda that a recursive function is terminating

我在说服 Agda 函数递归调用中的参数在结构上小于传入参数时遇到了一些困难。

我定义了对、对列表(将有限函数表示为 input/output 对中的 "sets")以及此类列表的并集,如下所示:

data _x_ {l : Level} (A B : Set l) : Set l where
  <_,_> : A -> B → A x B

data FinFun (A B : Set) : Set where
  nil : FinFun A B
  _::_ : A x B → FinFun A B → FinFun A B

_U_ : {A B : Set} -> FinFun A B -> FinFun A B -> FinFun A B
nil U f' = f'
(x :: xs) U f' = x :: (xs U f')

我还定义了 "neighborhoods" 和两个这样的邻域的上界:

data UniNbh : Set where
  bot : UniNbh
  lam : FinFun UniNbh UniNbh -> UniNbh

_u_ : UniNbh -> UniNbh -> UniNbh
bot u bot = bot
bot u (lam f) = lam f
(lam f) u bot = lam f
(lam f) u (lam f') = lam (f U f')

最后,对于这个问题最重要的是,我定义了一个函数,给定一个邻域对列表,它取列表中所有对的第一个分量的上界:

pre : FinFun UniNbh UniNbh -> UniNbh
pre nil = bot
pre (< x , y > :: f) = x u pre f

给我带来麻烦的互递归函数本质上是这样的:

f : UniNbh -> UniNbh -> UniNbh -> Result
-- Base cases here. When any argument is bot or lam nil, no
-- recursion is needed.
f (lam (a ∷ as)) (lam (b ∷ bs)) (lam (c ∷ cs)) =
  f (lam (a ∷ as)) (pre (b ∷ bs)) (lam (c ∷ cs))

pre f 小于 lam f,或者其中一个基本情况将结束递归似乎很明显,但 Agda 看不到这一点是可以理解的。为了解决这个问题,我尝试了很多不同的想法,但都没有奏效。在这一点上,我认为唯一的方法是使用标准库中的 Induction.WellFounded,但我不知道如何使用。

我未能成功证明以下数据类型是有根据的:

data preSmaller : UniNbh -> UniNbh -> Set where
  pre-base : preSmaller (pre nil) (lam nil)
  pre-step : ∀ (x y f f') ->
             preSmaller (pre f) (lam f') ->
             preSmaller  (pre (< x , y > :: f')) (lam (< x , y > :: f'))

我什至不确定这种数据类型是否有用,即使我可以证明它是有根据的。

当四处寻找有关如何使用 Induction.WellFounded 的信息时,我只能找到非常简单的例子来表明 < 对于自然数 是有根据的,但我无法概括这些想法对于这种情况。

抱歉这么久 post。任何帮助将不胜感激!

由于某些 unicode,我看不到全部定义 - 您引入的许多字符都呈现为正方形。 WellFounded 的基本思想并不是某种数据类型变小的证明。基本思想是 Agda 可以看到 Acc _<_ x 由包裹在 Acc _<_ y 中的访问函数构造的变得更小。

在你的情况下,preSmaller 似乎就是这样一个 _<_。很难判断是否是这样,因为缺少很多文本。然后你需要构造一个函数,可以为给定的任意两个 x y : UniNbh.

构建一个 Acc preSmaller y

编辑后的问题仍然遗漏了一些定义(例如,什么是 post nil。但我明白了正在发生的事情的要点。

您对 preSmaller 的定义类似于 Nat_<_ 的以下定义:

data _<_ : Nat -> Nat -> Set where
   z<  : {n : Nat} -> zero < (succ n)
   s<s : {m n : Nat} -> m < n -> (succ m) < (succ n)

请注意,它与标准定义不同,因为 mn 都变大了。这会影响 WellFounded-ness 证明的构建。

-- may just as well import, but let me be self-contained:
data Acc {A : Set} (_<_ : A -> A -> Set) (x : A) : Set where
   acc : ((y : A) -> y < x -> Acc _<_ y) -> Acc _<_ x

Well-founded : (A : Set) -> (R : A -> A -> Set) -> Set
Well-founded A _<_ = (x : A) -> Acc _<_ x

{-# BUILTIN EQUALITY _==_ #-} -- rewrite rule needs this, if I am not using
-- Unicode version of it from Prelude
<-Well-founded : Well-founded Nat _<_
<-Well-founded zero     = acc \_ ()
<-Well-founded (succ x) = acc aux where
   aux : (y : Nat) -> y < (succ x) -> Acc _<_ y
   aux zero     _                                 = <-Well-founded zero
   aux (succ y) (s<s y<x) with <-Well-founded x | is-eq? (succ y) x
   ...          | acc f | no  sy!=x               = f (succ y) (neq y<x sy!=x)
   ...          | wf-x  | yes sy==x rewrite sy==x = wf-x

辅助函数:

data False : Set where

false-elim : {A : Set} -> False -> A
false-elim ()

data Dec (A : Set) : Set where
   yes : A -> Dec A
   no  : (A -> False) -> Dec A

_==?_ : {A : Set} -> A -> A -> Set
_==?_ x y = Dec (x == y)

s== : {m n : Nat} -> (succ m) == (succ n) -> m == n
s== refl = refl

is-eq? : (m n : Nat) -> m ==? n
is-eq? zero     zero     = yes refl
is-eq? (succ m) zero     = no \()
is-eq? zero     (succ n) = no \()
is-eq? (succ m) (succ n) with is-eq? m n
...                        | no  f   = no \sm=sn -> f (s== sm=sn)
...                        | yes m=n = yes (cong succ m=n)

-- if m < n and m+1 /= n, then m+1 < n
neq : {m n : Nat} -> m < n -> ((succ m) == n -> False) -> (succ m) < n
neq {_}      {zero}          ()
neq {zero}   {succ zero}     z<        f = false-elim (f refl)
neq {zero}   {succ (succ n)} z<        f = s<s z<
neq {succ m} {succ n}        (s<s m<n) f = s<s (neq m<n \m=n -> f (cong succ m=n))

要带走的重要物品:

_<_ 的标准定义允许构建更简单的 WellFounded-ness 证明,因为可以一次递减一个参数。 _<_ 的不同定义需要减少两者,这似乎是一个问题。然而,使用辅助函数 neq 可以构造一个递归,其中只有一个和相同的参数变小。

_==_Nat 的可判定性允许我构建这样的递归。 Agda 可以看到对 <-WellFounded 的递归调用是针对结构较小的 x,因此终止。然后根据相等性测试的结果使用不同的结果。使用 neq 的分支计算必要的 Acc 给定 <-WellFounded 为更小的 x 发现的函数:函数终止,因为 Agda 允许构造这样的函数。另一个分支 x == (succ y) 按原样使用值,因为 rewrite 使 Agda 相信它是正确的类型。


通过构造 <-WellFounded:

的实例,然后可以使用有根据性来证明函数终止
_-|-_ : Bin -> Bin -> Bin
x -|- y with max-len x y
...   | n , (x<n , y<n) = Sigma.fst (a (<-Well-founded n) b (x , x<n) (y , y<n)) where
  a : {n : Nat} -> Acc _<_ n -> Bin -> S-Bin n -> S-Bin n -> S-Bin (succ n)
  a+O : {n : Nat} -> Acc _<_ n -> Bin -> S-Bin n -> S-Bin n -> S-Bin (succ (succ n))
  a+I : {n : Nat} -> Acc _<_ n -> Bin -> S-Bin n -> S-Bin n -> S-Bin (succ (succ n))

  a+O f c m n with a f c m n
  ...                | r , r<n = r O , s<s r<n

  a+I f c m n with a f c m n
  ...                | r , r<n = r I , s<s r<n

  a {zero} _ _ (_ , ())
  a {succ sz} (acc f) cc mm nn with cc | mm | nn
  ... | b | m O , s<s m< | n O , s<s n< = a+O (f sz n<n1) b (m , m<) (n , n<)
  ... | b | m O , s<s m< | n I , s<s n< = a+I (f sz n<n1) b (m , m<) (n , n<)
   ....-- not including the whole thing here - it is too long.

我不包括两个二进制数加法的整个构造(也不是有效的 - 只是证明有根据的练习)。这里要注意的重要一点是递归是如何开始的,以及它是如何被重用来构造新的 Acc 实例来匹配类型的——这里 S-Bin 表示最多位长度的二进制数 n,而 Agda 确信 Acc _<_ n 变小了,即使它不能证明 S-Bin n 变小。