Dafny Dafny
首页
  • 入门介绍

    • 什么是dafny?
  • 用起来吧!

    • 安装
    • 快速上手
    • 可能遇到的问题?
  • Dafny快速入门

    • 基础学习 Basic
    • 方法 Method
    • 关键字 Keyword
    • 函数 Function
    • 类 Class
    • 泛型 Generics
    • 声明 Statement
    • 表达式 Expression
  • Dafny简单例子

    • 寻找最大最小数和
    • 斐波那契数列
    • 线性查询
    • 引理-计算序列非负元素个数
    • 集合
    • 终止
  • Dafny指导

    • 介绍
    • 方法 Methods
    • 前置/后置条件 Pre/Postconditions
    • 断言 Assertions
    • 函数 Functions
    • 循环不变体 Loop Invariants
    • 数组 Arrays
    • 量词(函数) Quantifiers
    • 谓词(函数) Predicates
    • 框架 Framing
    • 二分搜索 Binary Search
    • 总结
  • Dafny进阶语法

    • 引理和归纳 Lemmas and Induction
    • 模块 Modules
    • 集合 sets
    • 序列 sequence
    • 终止 Terminal
    • 值类型 Values Types
  • 实践探索

    • 自动归纳
    • 自动调用引理
    • 定义、证明、算法正确性
    • 各种推导式
    • 不同类型的证明
    • 集合元素上的函数
    • 在集合上的迭代
  • 常用工具

    • Type System
    • Style Guide
    • Cheet Sheet
✨收藏
  • 简体中文
  • English
💬社区留言板
GitHub (opens new window)

Dafny

新一代验证语言
首页
  • 入门介绍

    • 什么是dafny?
  • 用起来吧!

    • 安装
    • 快速上手
    • 可能遇到的问题?
  • Dafny快速入门

    • 基础学习 Basic
    • 方法 Method
    • 关键字 Keyword
    • 函数 Function
    • 类 Class
    • 泛型 Generics
    • 声明 Statement
    • 表达式 Expression
  • Dafny简单例子

    • 寻找最大最小数和
    • 斐波那契数列
    • 线性查询
    • 引理-计算序列非负元素个数
    • 集合
    • 终止
  • Dafny指导

    • 介绍
    • 方法 Methods
    • 前置/后置条件 Pre/Postconditions
    • 断言 Assertions
    • 函数 Functions
    • 循环不变体 Loop Invariants
    • 数组 Arrays
    • 量词(函数) Quantifiers
    • 谓词(函数) Predicates
    • 框架 Framing
    • 二分搜索 Binary Search
    • 总结
  • Dafny进阶语法

    • 引理和归纳 Lemmas and Induction
    • 模块 Modules
    • 集合 sets
    • 序列 sequence
    • 终止 Terminal
    • 值类型 Values Types
  • 实践探索

    • 自动归纳
    • 自动调用引理
    • 定义、证明、算法正确性
    • 各种推导式
    • 不同类型的证明
    • 集合元素上的函数
    • 在集合上的迭代
  • 常用工具

    • Type System
    • Style Guide
    • Cheet Sheet
✨收藏
  • 简体中文
  • English
💬社区留言板
GitHub (opens new window)
  • Dafny教程

  • Dafny进阶语法

    • 引理和归纳
    • 模块
    • 集合
    • 序列
    • 终止
    • 值类型
  • 教程
  • Dafny进阶语法
lijiahai
2022-03-26

终止

# 终止

Dafny证明所有程序终止。有两个潜在的非终止(发散)行为来源:循环,递归函数和方法。Dafny使用一种技术来处理这两种情况,减少注释。

递减注释指定一个称为终止度量的值,该值在每次遍历循环或每次调用递归函数或方法时严格变小。这个值也是有界的,因此它不会永远减小。这样,如果值从任意有限值开始,循环或递归必须停止。为了证明这一点,Dafny证明了终止度量在每次迭代中变得更小。如果Dafny无法证明这一点,它表示没有减少终止措施。因为每一种终止措施都有一个内置的下界,这就是Dafny需要做的,以证明终止。

Dafny可以在递减注释中使用几种类型的值,但最常见的是整数。整数有一个自然的下界,0,而且通常很容易证明它们是递减的。由于许多循环遍历索引,这类终止证明非常常见。例如,我们可能有以下循环:

method m(n: nat)
{
   var i := 0;
   while i < n
      invariant 0 <= i <= n
   {
      // do something interesting
      i := i + 1;
   }
}

while i < n
   invariant 0 <= i <= n
{
   // do something interesting
   i := i + 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

如果我们把这个交给Dafny,就能立即证实。但是它是怎么知道它会终止的呢?因为这是如此常见的循环形式,Dafny有一个猜测终止度量的特殊规则。Dafny看到没有显式的递减注释,所以它尝试猜测一个。它看到循环条件是A < B形式的比较,对于某些A和B,所以它做出猜测:

  decreases B - A
1

如果这样的话

  decreases n - i
1

如果我们将这个注释添加到循环中,它将继续进行验证。Dafny实际上没有那么严格要求终止测度为0。它真正需要的是,当终止度量为负时,循环不会再次执行。所以我们可以这样写:

method m()
{
   var i, n := 0, 11;
   while i < n
      decreases n - i
   {
      // do something interesting
      i := i + 5;
   }
}

var i, n := 0, 11;
while i < n
   decreases n - i
{
   // do something interesting
   i := i + 5;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这里,在最后一次迭代中,i变成了15,所以终止测度是-4。但在这里,循环保护为假,所以循环不会再次执行。注意,我们必须删除循环不变式,因为i现在可以超过n。

Dafny证明了整个程序的终止,而不仅仅是循环。为此,它对递归函数和方法使用了相同的技术。Dafny分析哪些函数/方法彼此调用,以找出可能的递归。对于每个可能递归的函数/方法,它需要显式或隐式减少函数或方法上的注释。大多数递归函数/方法都是自递归的: Dafny证明了整个程序的终止,而不仅仅是循环。为此,它对递归函数和方法使用了相同的技术。Dafny分析哪些函数/方法彼此调用,以找出可能的递归。对于每个可能递归的函数/方法,它需要显式或隐式减少函数或方法上的注释。大多数递归函数/方法都是自递归的:

function fac(n: nat): nat
{
   if n == 0 then 1 else n * fac(n-1)
1
2
3

Dafny接受了这个计划。对于大多数递归函数来说,它们只是用更小的参数值来调用自己,所以参数减少是默认的猜测。可以通过添加以下内容:

decreases n
1

到函数声明来明确decreases注释

有时,具有可能不会终止的循环或终止证明未知的循环是有益的。例如,考虑以下方法:

method hail(N: nat)
   decreases *
{
   var n := N;
   while 1 < n
      decreases *
   {
      n := if n % 2 == 0 then n / 2 else n * 3 + 1;
   }
}
1
2
3
4
5
6
7
8
9
10

这个程序终止当且仅当Collatz猜想为真时,这是一个数学上的开放问题,所以你不能期望Dafny能够证明终止。您还可以编写流处理器之类的代码,以便永远运行。因此,Dafny提供了一个“out”,一个特殊的注释,指示Dafny不要试图证明终止,这在前面的hail方法中已经给出。这可以用于所有非鬼循环。注意,如果一个方法包含一个标记为decreases *的循环,那么它本身也必须被标记为decreases *。

Dafny可以使用整数以外的值作为终止度量。当指定序列时,Dafny自动使用长度作为终止度量。如果一个集合是另一个集合的严格子集,则认为这个集合更小,因此每个集合必须包含在前面的集合中。对于集合,空集合是尽可能小的,序列有自然数长度,所以这两个都有下界。虽然不是非常有用,但也可以在递减子句中使用bool和references。(如果你想了解细节,请参阅参考资料。)最后的终止度量是其他类型度量的元组。例如,下面的Ackermann函数的实现使用一对整数来证明终止

function Ack(m: nat, n: nat): nat
   decreases m, n
{
   if m == 0 then n + 1
   else if n == 0 then Ack(m - 1, 1)
   else Ack(m - 1, Ack(m, n - 1))
}
1
2
3
4
5
6
7

在这里,递减子句被明确地写了出来,尽管Dafny会猜出完全相同的事情。元组使用组件值的大小比较来确定度量值是否缩水。在这种情况下,它使用两个整数,但一般来说,不同的部分可以属于不同的类别。这种比较是按词典编纂的。如果第一个元素,在这里是m,比其他的值小没关系。它们可以增加,减少,或保持不变。只有当第一个元素不变时,才考虑第二个元素。然后,第二个值需要减小。如果没有,则第三个元素必须减少,以此类推。最终,必有一种元素会减少。过了这个点,其他元素就可以自由增加或做它们想做的事。 在Ack函数中,有三个递归调用。在第一种情况下,m变小1,但n增大。这很好,因为在元组中n在m后面。在第二个调用中,m也减少了,因此第二个参数允许是任何值(这很好,因为Dafny实际上并没有证明关于第三个递归调用的结果的任何东西)。Dafny确实需要证明第三通电话符合终止措施。对于这个调用,m保持不变,但n减小,因此总体度量也减小了。 终止不仅适用于单个函数/方法,还适用于多个相互递归的函数/方法。例如,考虑这对递归定义的奇偶校验谓词:

predicate even(n: nat)
   ensures even(n) <==> n % 2 == 0
{
   if n == 0 then true else odd(n-1)
}
predicate odd(n: nat)
   ensures odd(n) <==> n % 2 != 0
{
   if n == 0 then false else even(n-1)
}
1
2
3
4
5
6
7
8
9
10

Dafny通过考虑两个函数中所有可能的路径证明了它们的终止。

编辑 (opens new window)
上次更新: 2022/03/26, 19:35:15
序列
值类型

← 序列 值类型→

最近更新
01
寻找最大和最小数
04-06
02
斐波那契数列
04-06
03
线性查询
04-06
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Li Jiahai | Dafny Community | 2022
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式