Functional programming 协助Agda&x27;s终止检查器

Functional programming 协助Agda&x27;s终止检查器,functional-programming,termination,agda,Functional Programming,Termination,Agda,假设我们定义一个函数 f : N \to N f 0 = 0 f (s n) = f (n/2) -- this / operator is implemented as floored division. Agda将在鲑鱼身上画f,因为它无法判断n/2是否小于n。我不知道该怎么跟阿格达说。我在标准库中看到,它们有一个2的除法,并证明n/2几周前Agda邮件列表上出现了一个类似的问题,共识似乎是将Data.Nat元素注入Data.Bin中,然后在此表示上使用结构递归,这非常适合手头的工作 你可

假设我们定义一个函数

f : N \to N
f 0 = 0
f (s n) = f (n/2) -- this / operator is implemented as floored division.

Agda将在鲑鱼身上画f,因为它无法判断n/2是否小于n。我不知道该怎么跟阿格达说。我在标准库中看到,它们有一个2的除法,并证明n/2您不能直接这样做:Agda的终止检查器只在语法较小的参数上考虑递归ok。但是,提供了几个模块,用于使用函数参数之间有充分根据的顺序来证明终止。自然数的标准顺序就是这样的顺序,可以在这里使用

使用归纳法中的代码,您可以按如下方式编写函数:

open import Data.Nat
open import Induction.WellFounded
open import Induction.Nat

s≤′s : ∀ {n m} → n ≤′ m → suc n ≤′ suc m
s≤′s ≤′-refl = ≤′-refl
s≤′s (≤′-step lt) = ≤′-step (s≤′s lt)

proof : ∀ n → ⌊ n /2⌋ ≤′ n
proof 0 = ≤′-refl
proof 1 = ≤′-step (proof zero)
proof (suc (suc n)) = ≤′-step (s≤′s (proof n))

f : ℕ → ℕ
f = <-rec (λ _ → ℕ) helper
  where
    helper : (n : ℕ) → (∀ y → y <′ n → ℕ) → ℕ
    helper 0 rec = 0
    helper (suc n) rec = rec ⌊ n /2⌋ (s≤′s (proof n))
打开导入数据.Nat
开放式导入导入。有根据
开放式导入导入
s≤′s:∀ {n m}→ N≤′ M→ 苏克≤′ suc m
s≤′s≤′-refl=≤′-回流
s≤′(≤′-步骤lt)=≤′-步骤≤′(左)
证明:∀ N→ ⌊ n/2⌋ ≤′ N
证明0=≤′-回流
证明1=≤′-步骤(证明零)
证明(suc(suc n))=≤′-步骤≤′s(n))
f:ℕ → ℕ

f=Agda的终止检查器只检查结构递归(即在结构较小的参数上发生的调用),无法建立这种关系(如
\up>几周前Agda邮件列表上出现了一个类似的问题,共识似乎是将
Data.Nat
元素注入
Data.Bin
中,然后在此表示上使用结构递归,这非常适合手头的工作


你可以在这里找到整个线索:

在接受Vitus的答案后,我发现了一种不同的方法来实现证明函数在Agda中终止的目标,即使用“大小类型”。我在这里提供我的答案是因为它看起来是可以接受的,也是为了批评这个答案的任何弱点

尺寸类型如下所述:

它们在Agda中实现,而不仅仅是在MiniAgda中;请参见此处:

这个想法是用一个允许typechecker更容易证明终止的大小来扩充数据类型。大小在标准库中定义

open import Size
我们定义自然数:

data Nat : {i : Size} \to Set where
    zero : {i : Size} \to Nat {\up i} 
    succ : {i : Size} \to Nat {i} \to Nat {\up i}
接下来,我们定义前置和减法(monus):

现在,我们可以通过欧几里德算法定义除法:

div : {i : Size} → Nat {i} → Nat → Nat {i}
div .{↑ i} (zero {i}) n = zero {i}
div .{↑ i} (succ {i} m) n = succ {i} (div {i} (sub {i} m n) n)

data ⊥ : Set where
record ⊤ : Set where
notZero :  Nat → Set
notZero zero = ⊥
notZero _ = ⊤
我们对非零分母进行除法。 如果分母是非零的,那么它的形式是b+1 分区位置a(b+1)=分区a和分区b 由于a b部门返回上限(a/(b+1))

作为辅助:

div2 : {i : Size} → Nat {i} → Nat {i}
div2 n = divPos n (succ (succ zero)) (record {})
现在我们可以定义一种分而治之的方法来计算第n个斐波那契数

fibd : {i : Size} → Nat {i} → Nat
fibd zero = zero
fibd (succ zero) = succ zero
fibd (succ (succ zero)) = succ zero
fibd (succ n) with even (succ n)
fibd .{↑ i}  (succ {i} n) | true = 
  let
    -- When m=n+1, the input, is even, we set k = m/2
    -- Note, ceil(m/2) = ceil(n/2)
    k = div2 {i} n
    fib[k-1] = fibd {i} (pred {i} k)
    fib[k] = fibd {i} k
    fib[k+1] =  fib[k-1] + fib[k]
  in
    (fib[k+1] * fib[k]) + (fib[k] * fib[k-1])
fibd .{↑ i} (succ {i} n) | false = 
  let
    -- When m=n+1, the input, is odd, we set k = n/2 = (m-1)/2.
    k = div2 {i} n
    fib[k-1] = fibd {i} (pred {i} k)
    fib[k] = fibd {i} k
    fib[k+1] = fib[k-1] + fib[k]
  in
    (fib[k+1] * fib[k+1]) + (fib[k] * fib[k])

您可以避免使用基础良好的递归。假设您想要一个适用于
⌊_/2.⌋
转换为一个数字,直到它达到
0
,并收集结果。使用
{-#termining#-}
pragma可以如下定义:

open import Data.Nat
open import Relation.Binary

open DecTotalOrder decTotalOrder
  using (trans)
{-# TERMINATING #-}
⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s 0 = []
⌊_/2⌋s n = n ∷ ⌊ ⌊ n /2⌋ /2⌋s
第二条相当于

⌊_/2⌋s n = n ∷ ⌊ n ∸ (n ∸ ⌊ n /2⌋) /2⌋s
可以使
⌊_/2.⌋s
通过内联此减法进行结构递归:

⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s = go 0 where
  go : ℕ -> ℕ -> List ℕ
  go  _       0      = []
  go  0      (suc n) = suc n ∷ go (n ∸ ⌈ n /2⌉) n
  go (suc i) (suc n) = go i n
go(n∸ ⌈ n/2⌉) n
是go(suc n)的简化版本∸ ⌊ suc n/2⌋ ∸ 1) n

一些测试:

test-5 : ⌊ 5 /2⌋s ≡ 5 ∷ 2 ∷ 1 ∷ []
test-5 = refl

test-25 : ⌊ 25 /2⌋s ≡ 25 ∷ 12 ∷ 6 ∷ 3 ∷ 1 ∷ []
test-25 = refl
现在让我们假设您想要一个应用
⌊_/2.⌋
到一个数字,直到它达到
0
,并对结果求和

⌊_/2⌋sum : ℕ -> ℕ
⌊ n /2⌋sum = go ⌊ n /2⌋s where
  go : List ℕ -> ℕ
  go  []      = 0
  go (n ∷ ns) = n + go ns
因此,我们可以在一个列表上运行递归,该列表包含由
⌊_/2.⌋s
功能

更简洁的版本是

⌊ n /2⌋sum = foldr _+_ 0 ⌊ n /2⌋s
回到井的基础上

open import Function
open import Relation.Nullary
open import Relation.Binary
open import Induction.WellFounded
open import Induction.Nat

calls : ∀ {a b ℓ} {A : Set a} {_<_ : Rel A ℓ} {guarded : A -> Set b}
      -> (f : A -> A)
      -> Well-founded _<_
      -> (∀ {x} -> guarded x -> f x < x)
      -> (∀ x -> Dec (guarded x))
      -> A
      -> List A
calls {A = A} {_<_} f wf smaller dec-guarded x = go (wf x) where
  go : ∀ {x} -> Acc _<_ x -> List A
  go {x} (acc r) with dec-guarded x
  ... | no  _ = []
  ... | yes g = x ∷ go (r (f x) (smaller g))
⌊ n/2⌋s
仅当
n
0
时才返回
[]
,因此
guarded=λn->n>0


我们的良好基础关系是基于
我想提供一个与上面给出的答案稍有不同的答案。特别是,我想建议,与其试图说服终止检查器,实际上,不,这种递归是完全正确的,我们应该试着具体化良好基础,以便rec由于结构上的原因,ursion显然很好

这里的想法是,问题来自于看不到
n/2
在某种程度上是
n
的“一部分”。结构递归想要将事物分解为其直接部分,但
n/2
的方式是“一部分”当然,
n
是我们每隔一个
suc
丢弃一次。但是前面不清楚要丢弃多少次,我们必须环顾四周,并尝试将事情排成一行。如果我们有一些类型具有“多个”
suc
的构造函数,那就更好了

为了让问题稍微有趣一点,让我们试着定义一个函数,其行为类似

f : ℕ → ℕ
f 0 = 0
f (suc n) = 1 + (f (n / 2))
也就是说,应该是这样的

f n = ⌈ log₂ (n + 1) ⌉
当然,上面的定义不起作用,原因与你的
f
不起作用的原因相同。但是让我们假设它起作用,让我们探索“路径”,也就是说,这个参数将通过自然数来实现。假设我们看
n=8

f 8 = 1 + f 4 = 1 + 1 + f 2 = 1 + 1 + 1 + f 1 = 1 + 1 + 1 + 1 + f 0 = 1 + 1 + 1 + 1 + 0 = 4
所以“路径”是
8->4->2->1->0
。比如说,11呢

f 11 = 1 + f 5 = 1 + 1 + f 2 = ... = 4
所以“路径”是
11->5->2->1->0

很自然地,在每一步,我们要么除以2,要么减去1再除以2。每一个大于0的自然数都可以用这种方式进行唯一分解。如果是偶数,除以2继续,如果是奇数,减去1再除以2继续

因此,现在我们可以确切地看到我们的数据类型应该是什么样子。我们需要一个具有表示“两倍于
suc
”的构造函数的类型,另一个表示“两倍于
suc
”的类型,当然还有一个表示“零
suc
s”的构造函数:

现在我们可以定义分解的函数
open import Size
data Nat : {i : Size} \to Set where
    zero : {i : Size} \to Nat {\up i} 
    succ : {i : Size} \to Nat {i} \to Nat {\up i}
pred : {i : Size} → Nat {i} → Nat {i}
pred .{↑ i} (zero {i}) = zero {i}
pred .{↑ i} (succ {i} n) = n 

sub : {i : Size} → Nat {i} → Nat {∞} → Nat {i}
sub .{↑ i} (zero {i}) n = zero {i}
sub .{↑ i} (succ {i} m) zero = succ {i} m
sub .{↑ i} (succ {i} m) (succ n) = sub {i} m n
div : {i : Size} → Nat {i} → Nat → Nat {i}
div .{↑ i} (zero {i}) n = zero {i}
div .{↑ i} (succ {i} m) n = succ {i} (div {i} (sub {i} m n) n)

data ⊥ : Set where
record ⊤ : Set where
notZero :  Nat → Set
notZero zero = ⊥
notZero _ = ⊤
divPos : {i : Size} → Nat {i} → (m : Nat) → (notZero m) → Nat {i}
divPos a (succ b) p = div a b
divPos a zero ()
div2 : {i : Size} → Nat {i} → Nat {i}
div2 n = divPos n (succ (succ zero)) (record {})
fibd : {i : Size} → Nat {i} → Nat
fibd zero = zero
fibd (succ zero) = succ zero
fibd (succ (succ zero)) = succ zero
fibd (succ n) with even (succ n)
fibd .{↑ i}  (succ {i} n) | true = 
  let
    -- When m=n+1, the input, is even, we set k = m/2
    -- Note, ceil(m/2) = ceil(n/2)
    k = div2 {i} n
    fib[k-1] = fibd {i} (pred {i} k)
    fib[k] = fibd {i} k
    fib[k+1] =  fib[k-1] + fib[k]
  in
    (fib[k+1] * fib[k]) + (fib[k] * fib[k-1])
fibd .{↑ i} (succ {i} n) | false = 
  let
    -- When m=n+1, the input, is odd, we set k = n/2 = (m-1)/2.
    k = div2 {i} n
    fib[k-1] = fibd {i} (pred {i} k)
    fib[k] = fibd {i} k
    fib[k+1] = fib[k-1] + fib[k]
  in
    (fib[k+1] * fib[k+1]) + (fib[k] * fib[k])
{-# TERMINATING #-}
⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s 0 = []
⌊_/2⌋s n = n ∷ ⌊ ⌊ n /2⌋ /2⌋s
⌊_/2⌋s n = n ∷ ⌊ n ∸ (n ∸ ⌊ n /2⌋) /2⌋s
⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s = go 0 where
  go : ℕ -> ℕ -> List ℕ
  go  _       0      = []
  go  0      (suc n) = suc n ∷ go (n ∸ ⌈ n /2⌉) n
  go (suc i) (suc n) = go i n
test-5 : ⌊ 5 /2⌋s ≡ 5 ∷ 2 ∷ 1 ∷ []
test-5 = refl

test-25 : ⌊ 25 /2⌋s ≡ 25 ∷ 12 ∷ 6 ∷ 3 ∷ 1 ∷ []
test-25 = refl
⌊_/2⌋sum : ℕ -> ℕ
⌊ n /2⌋sum = go ⌊ n /2⌋s where
  go : List ℕ -> ℕ
  go  []      = 0
  go (n ∷ ns) = n + go ns
⌊ n /2⌋sum = foldr _+_ 0 ⌊ n /2⌋s
open import Function
open import Relation.Nullary
open import Relation.Binary
open import Induction.WellFounded
open import Induction.Nat

calls : ∀ {a b ℓ} {A : Set a} {_<_ : Rel A ℓ} {guarded : A -> Set b}
      -> (f : A -> A)
      -> Well-founded _<_
      -> (∀ {x} -> guarded x -> f x < x)
      -> (∀ x -> Dec (guarded x))
      -> A
      -> List A
calls {A = A} {_<_} f wf smaller dec-guarded x = go (wf x) where
  go : ∀ {x} -> Acc _<_ x -> List A
  go {x} (acc r) with dec-guarded x
  ... | no  _ = []
  ... | yes g = x ∷ go (r (f x) (smaller g))
⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s = calls {guarded = ?} ⌊_/2⌋ ? ? ?
⌊_/2⌋s = calls {guarded = λ n -> n > 0} ⌊_/2⌋ <-well-founded {!!} {!!}
open import Data.Nat.Properties

suc-⌊/2⌋-≤′ : ∀ n -> ⌊ suc n /2⌋ ≤′ n
suc-⌊/2⌋-≤′  0      = ≤′-refl
suc-⌊/2⌋-≤′ (suc n) = s≤′s (⌊n/2⌋≤′n n)

>0-⌊/2⌋-<′ : ∀ {n} -> n > 0 -> ⌊ n /2⌋ <′ n
>0-⌊/2⌋-<′ {suc n} (s≤s z≤n) = s≤′s (suc-⌊/2⌋-≤′ n)
⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s = calls ⌊_/2⌋ <-well-founded >0-⌊/2⌋-<′ (_≤?_ 1)
f : ℕ → ℕ
f 0 = 0
f (suc n) = 1 + (f (n / 2))
f n = ⌈ log₂ (n + 1) ⌉
f 8 = 1 + f 4 = 1 + 1 + f 2 = 1 + 1 + 1 + f 1 = 1 + 1 + 1 + 1 + f 0 = 1 + 1 + 1 + 1 + 0 = 4
f 11 = 1 + f 5 = 1 + 1 + f 2 = ... = 4
data Decomp : ℕ → Set where
  zero : Decomp zero
  2*_ : ∀ {n} → Decomp n → Decomp (n * 2)
  2*_+1 : ∀ {n} → Decomp n → Decomp (suc (n * 2))
decomp : (n : ℕ) → Decomp n
decomp zero = zero
decomp (suc n) = decomp n +1
_+1 : {n : ℕ} → Decomp n → Decomp (suc n)
zero +1 = 2* zero +1
(2* d) +1 = 2* d +1
(2* d +1) +1 = 2* (d +1)
flatten : {n : ℕ} → Decomp n → ℕ
flatten zero = zero
flatten (2* p) = suc (flatten p)
flatten (2* p +1 = suc (flatten p)
f : ℕ → ℕ
f n = flatten (decomp n)