简体   繁体   English

协助Agda的终止检查

[英]Assisting Agda's termination checker

Suppose we define a function 假设我们定义了一个函数

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

Agda will paint f in salmon because it cannot tell if n/2 is smaller than n. Agda将在三文鱼中绘制f,因为它无法判断n / 2是否小于n。 I don't know how to tell Agda's termination checker anything. 我不知道怎么告诉Agda的终止检查器。 I see in the standard library they have a floored division by 2 and a proof that n/2 < n. 我在标准库中看到它们有一个2的分区和n / 2 <n的证明。 However, I still fail to see how to get the termination checker to realize that recursion has been made on a smaller subproblem. 但是,我仍然没有看到如何让终止检查器意识到已经在较小的子问题上进行了递归。

Agda's termination checker only checks for structural recursion (ie calls that happen on structurally smaller arguments) and there's no way to establish that certain relation (such as _<_ ) implies that one of the arguments is structurally smaller. Agda的终止检查器只检查结构递归(即在结构上较小的参数上发生的调用),并且没有办法确定某个关系(例如_<_ )意味着其中一个参数在结构上较小。


Digression: Similar problem happens with positivity checker. 题外话:积极性检查发生类似的问题。 Consider the standard fix-point data type: 考虑标准的定点数据类型:

data μ_ (F : Set → Set) : Set where
  fix : F (μ F) → μ F

Agda rejects this because F may not be positive in its first argument. 阿格达拒绝这一点,因为F的第一个论点可能不是正面的。 But we cannot restrict μ to only take positive type functions, or show that some particular type function is positive. 但是我们不能将μ限制为仅采用正类型函数,或者表明某些特定类型函数是正数。


How do we normally show that a recursive functions terminates? 我们通常如何表明递归函数终止? For natural numbers, this is the fact that if the recursive call happens on strictly smaller number, we eventually have to reach zero and the recursion stops; 对于自然数,这是事实,如果递归调用发生在严格较小的数字上,我们最终必须达到零并且递归停止; for lists the same holds for its length; 对于列表,其长度相同; for sets we could use the strict subset relation; 对于集合,我们可以使用严格的子集关系; and so on. 等等。 Notice that "strictly smaller number" doesn't work for integers. 请注意,“严格较小的数字”不适用于整数。

The property that all these relations share is called well-foundedness. 所有这些关系所共有的财产被称为有根据的。 Informally speaking, a relation is well-founded if it doesn't have any infinite descending chains. 非正式地说,如果一个关系没有任何无限的下行链,那么它就是有根据的。 For example, < on natural numbers is well founded, because for any number n : 例如, < on natural numbers是有根据的,因为对于任何数字n

n > n - 1 > ... > 2 > 1 > 0

That is, the length of such chain is limited by n + 1 . 也就是说,这种链的长度受到n + 1限制。

on natural numbers, however, is not well-founded: 然而, 自然数字并不是很有根据:

n ≥ n ≥ ... ≥ n ≥ ...

And neither is < on integers: 并且< on整数:

n > n - 1 > ... > 1 > 0 > -1 > ...

Does this help us? 这对我们有帮助吗? It turns out we can encode what it means for a relation to be well-founded in Agda and then use it to implement your function. 事实证明,我们可以编码对于在Agda中有充分根据的关系意味着什么,然后使用它来实现您的功能。

For simplicity, I'm going to bake the _<_ relation into the data type. 为简单起见,我要将_<_关系烘焙到数据类型中。 First of all, we must define what it means for a number to be accessible: n is accessible if all m such that m < n are also accessible. 首先,我们必须定义什么意思了许多可访问: n是可访问的,如果所有的m ,使得m < n也可以访问。 This of course stops at n = 0 , because there are no m so that m < 0 and this statement holds trivially. 这当然在n = 0停止,因为没有m使m < 0并且这个陈述非常简单。

data Acc (n : ℕ) : Set where
  acc : (∀ m → m < n → Acc m) → Acc n

Now, if we can show that all natural numbers are accessible, then we showed that < is well-founded. 现在,如果我们能够证明所有自然数都是可访问的,那么我们就证明了<有充分理由。 Why is that so? 为什么会这样? There must be a finite number of the acc constructors (ie no infinite descending chain) because Agda won't let us write infinite recursion. 必须有一定数量的acc构造函数(即没有无限下行链),因为Agda不会让我们写无限递归。 Now, it might seem as if we just pushed the problem back one step further, but writing the well-foundedness proof is actually structurally recursive! 现在,似乎我们只是将问题推回了一步,但写出有根据的证据实际上是结构递归的!

So, with that in mind, here's the definition of < being well-founded: 因此,考虑到这一点,这里有<有充分根据的定义:

WF : Set
WF = ∀ n → Acc n

And the well-foundedness proof: 并且有充分的基础证明:

<-wf : WF
<-wf n = acc (go n)
  where
  go : ∀ n m → m < n → Acc m
  go zero    m       ()
  go (suc n) zero    _         = acc λ _ ()
  go (suc n) (suc m) (s≤s m<n) = acc λ o o<sm → go n o (trans o<sm m<n)

Notice that go is nicely structurally recursive. 请注意, go在结构上很好地递归。 trans can be imported like this: trans可以像这样导入:

open import Data.Nat
open import Relation.Binary

open DecTotalOrder decTotalOrder
  using (trans)

Next, we need a proof that ⌊ n /2⌋ ≤ n : 接下来,我们需要一个⌊ n /2⌋ ≤ n的证明:

/2-less : ∀ n → ⌊ n /2⌋ ≤ n
/2-less zero          = z≤n
/2-less (suc zero)    = z≤n
/2-less (suc (suc n)) = s≤s (trans (/2-less n) (right _))
  where
  right : ∀ n → n ≤ suc n
  right zero    = z≤n
  right (suc n) = s≤s (right n)

And finally, we can write your f function. 最后,我们可以编写你的f函数。 Notice how it suddenly becomes structurally recursive thanks to Acc : the recursive calls happen on arguments with one acc constructor peeled off. 请注意,由于Acc它突然变得结构递归:递归调用发生在一个acc构造函数剥离的参数上。

f : ℕ → ℕ
f n = go _ (<-wf n)
  where
  go : ∀ n → Acc n → ℕ
  go zero    _       = 0
  go (suc n) (acc a) = go ⌊ n /2⌋ (a _ (s≤s (/2-less _)))

Now, having to work directly with Acc isn't very nice. 现在,不得不直接与Acc并不是很好。 And that's where Dominique's answer comes in. All this stuff I've written here has already been done in the standard library. 这就是Dominique的答案所在。我在这里写的所有这些东西都已经在标准库中完成了。 It is more general (the Acc data type is actually parametrized over the relation) and it allows you to just use <-rec without having to worry about Acc . 它更通用( Acc数据类型实际上是在关系上进行参数化),它允许您只需使用<-rec而不必担心Acc


Taking a more closer look, we are actually pretty close to the generic solution. 更仔细地看,我们实际上非常接近通用解决方案。 Let's see what we get when we parametrize over the relation. 让我们看看当我们对关系进行参数化时我们得到了什么。 For simplicity I'm not dealing with universe polymorphism. 为简单起见,我不处理宇宙多态性。

A relation on A is just a function taking two A s and returning Set (we could call it binary predicate): 在一个关系A只是采取两个功能A S和返回Set (我们可以称之为二元谓词):

Rel : Set → Set₁
Rel A = A → A → Set

We can easily generalize Acc by changing the hardcoded _<_ : ℕ → ℕ → Set to an arbitrary relation over some type A : 我们可以通过更改硬编码_<_ : ℕ → ℕ → Set为某种类型A的任意关系来轻松概括Acc

data Acc {A} (_<_ : Rel A) (x : A) : Set where
  acc : (∀ y → y < x → Acc _<_ y) → Acc _<_ x

The definition of well-foundedness changes accordingly: 有根据的定义也相应改变:

WellFounded : ∀ {A} → Rel A → Set
WellFounded _<_ = ∀ x → Acc _<_ x

Now, since Acc is an inductive data type like any other, we should be able to write its eliminator. 现在,由于Acc是一种与其他类似的归纳数据类型,我们应该能够编写它的消除器。 For inductive types, this is a fold (much like foldr is eliminator for lists) - we tell the eliminator what to do with each constructor case and the eliminator applies this to the whole structure. 对于感性的类型,这是一个倍(很像foldr是名单消除) -我们告诉消除做什么用的每个构造情况和消除应用此整个结构。

In this case, we'll do just fine with the simple variant: 在这种情况下,我们可以使用简单的变体做得很好:

foldAccSimple : ∀ {A} {_<_ : Rel A} {R : Set} →
                (∀ x → (∀ y → y < x → R) → R) →
                ∀ z → Acc _<_ z → R
foldAccSimple {R = R} acc′ = go
  where
  go : ∀ z → Acc _ z → R
  go z (acc a) = acc′ z λ y y<z → go y (a y y<z)

If we know that _<_ is well-founded, we can skip the Acc _<_ z argument completly, so let's write small convenience wrapper: 如果我们知道_<_是有根据的,我们可以完全跳过Acc _<_ z参数,所以让我们编写一个小的便利包装器:

recSimple : ∀ {A} {_<_ : Rel A} → WellFounded _<_ → {R : Set} →
            (∀ x → (∀ y → y < x → R) → R) →
            A → R
recSimple wf acc′ z = foldAccSimple acc′ z (wf z)

And finally: 最后:

<-wf : WellFounded _<_
<-wf = {- same definition -}

<-rec = recSimple <-wf

f : ℕ → ℕ
f = <-rec go
  where
  go : ∀ n → (∀ m → m < n → ℕ) → ℕ
  go zero    _ = 0
  go (suc n) r = r ⌊ n /2⌋ (s≤s (/2-less _))

And indeed, this looks (and works) almost like the one in the standard library! 事实上,这看起来(和工作)几乎像标准库中的那个!


Here's the fully dependent version in case you are wondering: 这是完全依赖的版本,以防您想知道:

foldAcc : ∀ {A} {_<_ : Rel A} (P : A → Set) →
          (∀ x → (∀ y → y < x → P y) → P x) →
          ∀ z → Acc _<_ z → P z
foldAcc P acc′ = go
  where
  go : ∀ z → Acc _ z → P z
  go _ (acc a) = acc′ _ λ _ y<z → go _ (a _ y<z)

rec : ∀ {A} {_<_ : Rel A} → WellFounded _<_ →
      (P : A → Set) → (∀ x → (∀ y → y < x → P y) → P x) →
      ∀ z → P z
rec wf P acc′ z = foldAcc P acc′ _ (wf z)

I would like to offer a slightly different answer than the ones given above. 我想提供一个与上面给出的略有不同的答案。 In particular, I want to suggest that instead of trying to somehow convince the termination checker that actually, no, this recursion is perfectly fine, we should instead try to reify the well-founded-ness so that the recursion is manifestly fine in virtue of being structural. 特别是,我想建议,而不是试图以某种方式说服终止检查器,实际上,不,这个递归是完全正常的,我们应该尝试重新建立有根据的ness,以便递归显然是好的,因为结构化。

The idea here is that the problem comes from being unable to see that n / 2 is somehow a "part" of n . 这里的想法是问题来自于无法看到n / 2在某种程度上是n的“部分”。 Structural recursion wants to break a thing into its immediate parts, but the way that n / 2 is a "part" of n is that we drop every other suc . 结构递归想打破的东西放在它的直接的部分,但该方式n / 2是的“一部分” n是我们放弃所有其他suc But it's not obvious up front how many to drop, we have to look around and try to line things up. 但事先并不明显有多少下降,我们必须四处寻找并尝试排队。 What would be nice is if we had some type that had constructors for "multiple" suc s. 什么将是很好,如果我们有一些类型已经为“多”构造suc秒。

To make the problem slightly more interesting, let's instead try to define the function that behaves like 为了使问题稍微有趣,让我们尝试定义行为类似的函数

f : ℕ → ℕ
f 0 = 0
f (suc n) = 1 + (f (n / 2))

that is to say, it should be the case that 也就是说,应该是这样的

f n = ⌈ log₂ (n + 1) ⌉

Now naturally the above definition won't work, for the same reasons your f won't. 现在自然上面的定义不起作用,原因与你的f不相同。 But let's pretend that it did, and let's explore the "path", so to speak, that the argument would take through the natural numbers. 但是让我们假装它做了,让我们探索“道路”,可以说,这个论点将通过自然数字。 Suppose we look at n = 8 : 假设我们看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

so the "path" is 8 -> 4 -> 2 -> 1 -> 0 . 所以“路径”是8 -> 4 -> 2 -> 1 -> 0 What about, say, 11? 比方说,11?

f 11 = 1 + f 5 = 1 + 1 + f 2 = ... = 4

so the "path" is 11 -> 5 -> 2 -> 1 -> 0 . 所以“路径”是11 -> 5 -> 2 -> 1 -> 0

Well naturally what's going on here is that at each step we're either dividing by 2, or subtracting one and dividing by 2. Every naturally number greater than 0 can be decomposed uniquely in this fashion. 很自然地,这里发生的是,在每一步我们要么除以2,要么减去1并除以2.每个自然数大于0都可以这种方式唯一地分解。 If it's even, divide by two and proceed, if it's odd, subtract one and divide by two and proceed. 如果它是偶数,则除以2并继续,如果它是奇数,则减1并除以2然后继续。

So now we can see exactly what our data type should look like. 所以现在我们可以确切地看到我们的数据类型应该是什么样子。 We need a type that has a constructor that means "twice as many suc 's" and another that means "twice as many suc 's plus one", as well as of course a constructor that means "zero suc s": 我们需要有一个构造函数,这意味着一个类型“的两倍多suc的”,另一个意思是“两倍多suc的加一”,以及,当然,这意味着‘零构造suc的’:

data Decomp : ℕ → Set where
  zero : Decomp zero
  2*_ : ∀ {n} → Decomp n → Decomp (n * 2)
  2*_+1 : ∀ {n} → Decomp n → Decomp (suc (n * 2))

We can now define the function that decomposes a natural number into the Decomp that corresponds to it: 我们现在可以定义将自然数分解为Decomp对应的Decomp的函数:

decomp : (n : ℕ) → Decomp n
decomp zero = zero
decomp (suc n) = decomp n +1

It helps to define +1 for Decomp s: 它有助于为Decomp定义+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)

Given a Decomp , we can flatten it down into a natural number that ignores the distinctions between 2*_ and 2*_+1 : 给定Decomp ,我们可以将其展平为一个自然数,忽略2*_2*_+1之间的区别:

flatten : {n : ℕ} → Decomp n → ℕ
flatten zero = zero
flatten (2* p) = suc (flatten p)
flatten (2* p +1 = suc (flatten p)

And now it's trivial to define f : 现在定义f是微不足道的:

f : ℕ → ℕ
f n = flatten (decomp n)

This happily passes the termination checker with no trouble, because we're never actually recursing on the problematic n / 2 . 这很愉快地通过终止检查器没有问题,因为我们实际上从未在有问题的n / 2上进行递归。 Instead, we convert the number into a format that directly represents its path through the number space in a structurally recursive way. 相反,我们将数字转换为以结构递归方式直接表示其通过数字空间的路径的格式。

Edit It occurred to me only a little while ago that Decomp is a little-endian representation of binary numbers. 编辑我刚刚发生了一段时间, Decomp是二进制数的小端表示。 2*_ is "append 0 to the end/shift left 1 bit" and 2*_+1 is "append 1 to the end/shift left 1 bit and add one". 2*_是“向末尾追加0 /向左移位1位”, 2*_+1是“向末尾追加1 /向左移1位并加1”。 So the above code is really about showing that binary numbers are structurally recursive wrt dividing by 2, which they ought to be! 所以上面的代码实际上是关于显示二进制数在结构上是递归的,除以2,它们应该是! That makes it much easier to understand, I think, but I don't want to change what I wrote already, so we could instead do some renaming here: Decomp ~> Binary , 2*_ ~> _,zero , 2*_+1 ~> _,one , decomp ~> natToBin , flatten ~> countBits . 这让我更容易理解,但我不想改变我已写的内容,所以我们可以在这里重命名: Decomp > Binary2*_ ~> _,zero2*_+1 〜> _,onedecomp 〜> natToBinflatten 〜> countBits

After accepting Vitus' answer, I discovered a different way to accomplish the goal of proving a function terminates in Agda, namely using "sized types." 在接受了Vitus的回答之后,我发现了一种不同的方法来实现证明函数终止于Agda的目标,即使用“大小类型”。 I am providing my answer here because it seems acceptable, and also for critique of any weak points of this answer. 我在这里提供我的答案,因为它似乎是可以接受的,也是对这个答案的任何弱点的批评。

Sized types are described: http://arxiv.org/pdf/1012.4896.pdf 描述了大小类型: http//arxiv.org/pdf/1012.4896.pdf

They are implemented in Agda, not only MiniAgda; 它们在Agda中实现,而不仅仅是MiniAgda; see here: http://www2.tcs.ifi.lmu.de/~abel/talkAIM2008Sendai.pdf . 见这里: http//www2.tcs.ifi.lmu.de/~abel/talkAIM2008Sendai.pdf

The idea is to augment the data type with a size that allows the typechecker to more easily prove termination. 我们的想法是增加数据类型,其大小允许类型检查器更容易证明终止。 Size is defined in the standard library. 大小在标准库中定义。

open import Size

We define sized natural numbers: 我们定义大小的自然数:

data Nat : {i : Size} \to Set where
    zero : {i : Size} \to Nat {\up i} 
    succ : {i : Size} \to Nat {i} \to Nat {\up i}

Next, we define predecessor and subtraction (monus): 接下来,我们定义前驱和减法(monus):

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

Now, we may define division via Euclid's algorithm: 现在,我们可以通过Euclid算法定义除法:

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 _ = ⊤

We give division for nonzero denominators. 我们给非零分母划分。 If the denominator is nonzero, then it is of the form, b+1. 如果分母非零,则其形式为b + 1。 We then do divPos a (b+1) = div ab Since div ab returns ceiling (a/(b+1)). 然后我们做divPos a(b + 1)= div ab因为div ab返回ceiling(a /(b + 1))。

divPos : {i : Size} → Nat {i} → (m : Nat) → (notZero m) → Nat {i}
divPos a (succ b) p = div a b
divPos a zero ()

As auxiliary: 作为辅助:

div2 : {i : Size} → Nat {i} → Nat {i}
div2 n = divPos n (succ (succ zero)) (record {})

Now we can define a divide and conquer method for computing the n-th Fibonacci number. 现在我们可以定义一种用于计算第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])

You cannot do this directly: Agda's termination checker only considers recursion ok on arguments that are syntactically smaller. 你不能直接这样做:Agda的终止检查器只考虑语法上较小的参数的递归。 However, the Agda standard library provides a few modules for proving termination using a well-founded order between the arguments of the functions. 但是, Agda标准库提供了一些模块,用于使用函数参数之间有充分理由的顺序来证明终止。 The standard order on natural numbers is such an order and can be used here. 自然数的标准顺序就是这样的顺序,可以在这里使用。

Using the code in Induction.*, you can write your function as follows: 使用Induction。*中的代码,您可以按如下方式编写函数:

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))

I found an article with some explanation here . 我在这里找到了一篇有一些解释的文章。 But there may be better references out there. 但可能有更好的参考。

A similar question appeared on the Agda mailing-list a few weeks ago and the consensus seemed to be to inject the Data.Nat element into Data.Bin and then use structural recursion on this representation which is well-suited for the job at hand. 几周前在Agda邮件列表上出现了一个类似的问题,似乎是将Data.Nat元素注入到Data.Bin ,然后在这种表示上使用结构递归,这非常适合手头的工作。

You can find the whole thread here : http://comments.gmane.org/gmane.comp.lang.agda/5690 你可以在这里找到整个主题: http//comments.gmane.org/gmane.comp.lang.agda/5690

You can avoid using well-founded recursion. 您可以避免使用有根据的递归。 Let's say you want a function, that applies ⌊_/2⌋ to a number, until it reaches 0 , and collects the results. 假设你需要一个函数,它将⌊_/2⌋应用于一个数字,直到它达到0 ,然后收集结果。 With the {-# TERMINATING #-} pragma it can be defined like this: 使用{-# TERMINATING #-} pragma,可以像这样定义:

{-# TERMINATING #-}
⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s 0 = []
⌊_/2⌋s n = n ∷ ⌊ ⌊ n /2⌋ /2⌋s

The second clause is equivalent to 第二个条款相当于

⌊_/2⌋s n = n ∷ ⌊ n ∸ (n ∸ ⌊ n /2⌋) /2⌋s

It's possible to make ⌊_/2⌋s structurally recursive by inlining this substraction: 通过内联这个减法可以使⌊_/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 is a simplified version of go (suc n ∸ ⌊ suc n /2⌋ ∸ 1) n go (n ∸ ⌈ n /2⌉) ngo (suc n ∸ ⌊ suc n /2⌋ ∸ 1) n的简化版本go (suc n ∸ ⌊ suc n /2⌋ ∸ 1) n

Some tests: 一些测试:

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

test-25 : ⌊ 25 /2⌋s ≡ 25 ∷ 12 ∷ 6 ∷ 3 ∷ 1 ∷ []
test-25 = refl

Now let's say you want a function, that applies ⌊_/2⌋ to a number, until it reaches 0 , and sums the results. 现在假设你需要一个函数,它将⌊_/2⌋应用于一个数字,直到它达到0 ,然后对结果求和。 It's simply 这很简单

⌊_/2⌋sum : ℕ -> ℕ
⌊ n /2⌋sum = go ⌊ n /2⌋s where
  go : List ℕ -> ℕ
  go  []      = 0
  go (n ∷ ns) = n + go ns

So we can just run our recursion on a list, that contains values, produced by the ⌊_/2⌋s function. 所以我们可以在包含值的列表上运行递归,这些值由⌊_/2⌋s函数生成。

More concise version is 更简洁的版本是

⌊ n /2⌋sum = foldr _+_ 0 ⌊ n /2⌋s

And back to the well-foundness. 并回到了良好的发现。

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))

This function does the same as the ⌊_/2⌋s function, ie produces values for recursive calls, but for any function, that satisfies certain conditions. 此函数与⌊_/2⌋s函数的作用相同,即为递归调用生成值,但对于满足某些条件的任何函数。

Look at the definition of go . 看看go的定义。 If x is not guarded , then return [] . 如果x没有被guarded ,则返回[] Otherwise prepend x and call go on fx (we could write go {x = fx} ... ), which is structurally smaller. 否则在前面加上x ,并呼吁gofx (我们可以写go {x = fx} ... ),这是结构更小。

We can redefine ⌊_/2⌋s in terms of calls : 我们可以在calls方面重新定义⌊_/2⌋s

⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s = calls {guarded = ?} ⌊_/2⌋ ? ? ?

⌊ n /2⌋s returns [] , only when n is 0 , so guarded = λ n -> n > 0 . ⌊ n /2⌋s返回[] ,仅当n0guarded = λ n -> n > 0 ⌊ n /2⌋s guarded = λ n -> n > 0

Our well-founded relation is based on _<′_ and defined in the Induction.Nat module as <-well-founded . 我们有充分理由的关系基于_<′_并在Induction.Nat模块中定义为<-well-founded

So we have 所以我们有

⌊_/2⌋s = calls {guarded = λ n -> n > 0} ⌊_/2⌋ <-well-founded {!!} {!!}

The type of the next hole is {x : ℕ} → x > 0 → ⌊ x /2⌋ <′ x 下一个洞的类型是{x : ℕ} → x > 0 → ⌊ x /2⌋ <′ x

We can easily prove this proposition: 我们可以很容易地证明这个命题:

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)

The type of the last hole is (x : ℕ) → Dec (x > 0) , we can fill it by _≤?_ 1 . 最后一个洞的类型是(x : ℕ) → Dec (x > 0) ,我们可以用_≤?_ 1填充它。

And the final definition is 最后的定义是

⌊_/2⌋s : ℕ -> List ℕ
⌊_/2⌋s = calls ⌊_/2⌋ <-well-founded >0-⌊/2⌋-<′ (_≤?_ 1)

Now you can recurse on a list, produced by ⌊_/2⌋s , without any termination issues. 现在,您可以在列表中递归,由⌊_/2⌋s ,没有任何终止问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM