Dafny 递归断言违规

Dafny recursive assertion violation

我是 dafny 的新手,正在尝试让这段简单的代码工作。我想计算字符串中 char 的出现次数。我在第 4 行收到断言违规。我知道我的函数正在查找正确数量的字符,但显然此断言中存在一些漏洞。在开始使用 pre 和 post 条件之前,我试图了解基础知识,而没有它们应该是可能的。该函数简单地检查字符串中的最后一个字符和 returns 1 或 0,并再次调用该函数以切断字符串的尾部直到它为空。

method Main() {
  var s:string := "hello world";
  print tally(s, 'l');
  assert tally(s,'l') == 3;
}
function method tally(s: string, letter: char): nat
{
  if |s| == 0 then 0
  else if s[|s|-1] == letter then 1+tally(s[..|s|-1], letter)
  else 0 + tally(s[..|s|-1], letter)
}

http://rise4fun.com/Dafny/2lvt 这是我的代码的 link。

很自然地认为 Dafny 静态验证器可以 评估任何代码,例如 assert 语句中的表达式。这 验证者确实尝试评估像这样的表达式,其中 参数作为常量给出(比如你的 "hello world", 'l', 和 3)。然而,静态验证者想要避免递归 永远(甚至递归太久),所以它并不总是完全 通过这些表达式。在你的情况下,也有限制 验证者能够对序列操作做什么。所以,在 简而言之,虽然验证者试图提供帮助,但并不总是 深入了解递归。

您可以通过两种方式解决这些限制以获得 验证者接受你的断言。

调试情况最可靠的方法是从较小的开始 输入并建立到您正在使用的更长的输入。这是相当 然而,这很乏味,当 Dafny 能够做到时,你会很感激 这些东西是自动的。以下(验证) 说明你会做什么:

var s := "hello world";
assert tally("he",'l') == 0;
assert tally("hel",'l') == 1;
assert "hell"[..3] == "hel";
assert tally("hell",'l') == 2;
assert "hello"[..4] == "hell";
assert tally("hello",'l') == 2;
assert "hello "[..5] == "hello";
assert tally("hello ",'l') == 2;
assert "hello w"[..6] == "hello ";
assert tally("hello w",'l') == 2;
assert "hello wo"[..7] == "hello w";
assert tally("hello wo",'l') == 2;
assert "hello wor"[..8] == "hello wo";
assert tally("hello wor",'l') == 2;
assert "hello worl"[..9] == "hello wor";
assert tally("hello worl",'l') == 3;
assert s[..10] == "hello worl";
assert tally(s,'l') == 3;

其实Dafny验证器没有展开的东西(太多了 次)对你来说是 "take" 操作(即表达式 形式 s[..E])。以下中间断言也将 验证自己并将有助于验证最终断言。这些 中间断言表明验证者不想做什么 自动为您服务。

var s := "hello world";
assert "he"[..1] == "h";
assert "hel"[..2] == "he";
assert "hell"[..3] == "hel";
assert "hello"[..4] == "hell";
assert "hello "[..5] == "hello";
assert "hello w"[..6] == "hello ";
assert "hello wo"[..7] == "hello w";
assert "hello wor"[..8] == "hello wo";
assert "hello worl"[..9] == "hello wor";
assert s[..10] == "hello worl";
assert tally(s,'l') == 3;

您可能想知道,"how in the world would I come up with this?"。这 最系统的方法是像我上面的第一个例子一样开始。 然后,您可以尝试修剪那里的许多断言,看看它是什么 是验证者的需求。

(我现在在想也许 Dafny 验证器可以增强到 也做这些操作。可能会导致性能问题 别处。我去看看。)

绕过验证器限制的另一种方法是定义 以不同的方式运行 tally,特别是避免 "take" 操作,验证者不想扩展太多。这是 不清楚要更改哪些内容才能使验证者满意 情况,或者如果这完全可能,那么这个解决方法可能 不是最好的。不过,我尝试了以下定义 tally 它使你的断言通过:

function method tally'(s: string, letter: char): nat
{
  tally_from(s, letter, 0)
}
function method tally_from(s: string, letter: char, start: nat): nat
  requires start <= |s|
  decreases |s| - start
{
  if start == |s| then 0
  else (if s[start] == letter then 1 else 0) + tally_from(s, letter, start+1)
}

请注意,这些定义不使用任何 "take" 操作。这里, 验证者很乐意扩展所有递归调用,直到 找到最终答案。

鲁斯坦