涉及序列的愚蠢代码中缺少不变量

missing invariant in dafny code involving sequences

我想知道 dafny 无法验证我的程序是否有原因?

https://rise4fun.com/Dafny/Ip1s

我是否遗漏了一些额外的不变量?

问题是您对 s 的定义和对 o 的构造进入了 "different directions"。 s 的递归情况根据以下定义 s(i) i[0]以及s(i[1..])定义的"previously"是什么。相反,循环迭代根据 i[n]o 的先前值定义新的 o。在你当前的程序中建立证明义务需要一个归纳证明的引理,而 Dafny 并没有自己发明这样的引理。

为了记录本回答,以下是您的开头:

function s(i: seq<int>): seq<int> {
  if |i| == 0 then [] else 
    if i[0] == 42 then [i[0]] + s(i[1..])
    else s(i[1..])
}

method q (i: seq<int>) returns (o: seq<int>)
  ensures o == s(i)
{
  var n := 0;
  o := [];

  while n < |i|
    invariant n <= |i| && o == s(i[..n])   
  {
    if i[n] == 42 {
      o := o + [i[n]];
    } 
    n := n + 1;
  } 
}

有四种出路。

一种解决方法是定义 s 的不同版本,称之为 s',它从给定序列的另一端递归。然后,在您的方法规范和循环不变式中将 s 替换为 s'。这是一个很好的解决方案,除非出于某种原因你真的更喜欢 s,而不是 s',在你的方法规范中。

第二个出路是定义这样一个s'并证明一个引理s(i)s'(i)return相同的值。这将使您在方法规范中保留 s,代价是有两个函数定义并且必须编写(并证明和使用)引理。

第三种出路是将循环更改为迭代 "downward" 而不是 "upward"。即在|i|处开始n,在循环体中递减n。 (和往常一样,n 的递增通常最好在循环体的末尾完成(post-递增),而 n 的递减通常最好在循环体的开头完成循环体(预递减)。)

第四个出路是改变关于 o 的循环不变量的编写方式。目前,不变量表示您已经计算出的内容,即 o == s(i[..n])。您可以根据尚未计算的内容编写不变量,如 o + s(i[n..]) == s(i),您可以将其读作 "once I have added s(i[n..]) to o, I will have s(i)"。这是 q 的那个版本:

method q(i: seq<int>) returns (o: seq<int>)
  ensures o == s(i)
{  
  var n := 0;
  o := [];

  while n < |i|
    invariant n <= |i| && o + s(i[n..]) == s(i)
  {
    if i[n] == 42 {
      o := o + [i[n]];
    } 
    n := n + 1;
  } 
}

您可能也有兴趣观看有关此主题的 this episode of Verification Corner

鲁斯坦