在 Dafny 中用于表现奇怪的序列的运算符

in operator for sequences comporting weird in Dafny

我如何帮助 Dafny 证明以下两个断言是相同的:

method foo(xs : seq<int>)
  requires forall x :: x in xs ==> 0 <= x < 5;
{
  assert forall x :: x in xs ==> 0 <= x < 5;
  assert forall i :: 0 <= i < |xs| ==> 0 <= xs[i] < 5;
}

另外,Dafny 似乎可以证明以下是相同的。这是为什么?


predicate test(value : int) {
  0 <= value < 5
}

method foo'(xs : seq<int>)
  requires forall x :: x in xs ==> test(x);
{
  assert forall i :: 0 <= i < |xs| ==> test(xs[i]);
}

Example on Rise4Fun

这与量词触发器有关。您可以在 Dafny FAQ.

中阅读有关触发器的更多信息

在这种情况下,第一个示例中最终断言失败的原因是 requires 子句和第一个断言中的 forall 量词在 x in xs 上触发。1 这意味着这个量化事实 将不会在值 v 处使用 除非 v in xs 是 "in scope"对于验证者。为了证明第二个断言,验证者需要使用更早的量化事实,其值为 xs[i],但 xs[i] in xs 不是 "in scope"。这听起来可能很奇怪,因为 xs[i] in xs 总是正确的,但事实证明,如果没有你的帮助,验证者无法解决这个问题。

所以要证明第二个断言,您需要以某种方式在范围内得到事实 xs[i] in xs。一种方法是将断言更改为

assert forall i :: 0 <= i < |xs| ==> xs[i] in xs && 0 <= xs[i] < 5;

(在结论中添加 xs[i] in xs)。事实上,一旦这个新的断言被证明,Dafny 就可以 然后 证明你之后的第二个断言,因为这个新的断言是在 xs[i] 上触发的,它也是 "in scope"在你的第二个断言中。

最后,你的第二个例子验证的原因是因为引入谓词test改变了可用的触发器。现在 requires 子句中的 forallx in xs test(x) 上触发。新触发器与 foo 正文中断言的新版本相匹配,因为 test(xs[i]) 已经出现。这会导致第一个 forall 被正确实例化,这意味着断言通过。像这样引入 otherwise-useless 命名谓词是按摩触发器以使它们执行您想要的操作时的常用技巧。


1. 您可以通过将鼠标悬停在 IDE 中的 forall 上,或通过 运行 在命令行上使用选项 /printTooltips 来查看正在使用的触发器。对于第一个示例中的第一个断言,您应该会看到类似 Selected triggers: {x in xs} 的内容。