I am new to dafny and am trying to get this simple piece of code to work. I want to count the occurrences of a char in a string. I am receiving an assertion violation on line 4. I know my function is finding the right amount of characters, but clearly there are some holes in this assertion. I'm trying to get the basics down before I start using pre and post conditions and what not, and this should be possible without them. The function simply checks the last character in the string and returns a 1 or 0, along with calling the function again which cuts off the tail of the string until it is empty.
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 Here is the link to my code.
It would be natural to think that the Dafny static verifier can
evaluate any code, like the expression in your assert statement. The
verifier does try to evaluate expressions like these where the
arguments are given as constants (like your "hello world"
, 'l'
,
and 3
). However, the static verifier wants to avoid recursing
forever (or even recursing for too long), so it doesn't always fully
go through these expressions. In your case, there are also limits to
what the verifier is able to do with the sequence operations. So, in
short, although the verifier tries to be helpful, it does not always
get to the bottom of the recursions.
There are two ways you can work around these limits to get the verifier to accept your assertion.
The most reliable way to debug the situation is to start with smaller inputs and build up to the longer inputs you're using. This is quite tedious, however, and it makes you appreciate when Dafny is able to do these things automatically. The following (which verifies) illustrates what you would do:
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;
In fact, the thing that the Dafny verifier does not expand (too many
times) for you are the "take" operations (that is, the expressions of
the form s[..E]
). The following intermediate assertions will also
verify themselves and will help to verify the final assertion. These
intermediate assertions show what the verifier doesn't think to do
automatically for you.
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;
You might wonder, "how in the world would I come up with this?". The most systematic way would be to start like in my first example above. Then, you could try pruning the many assertions there to see what it is the verifier needs.
(I'm now thinking that perhaps the Dafny verifier could be enhanced to do these operations, too. It may cause performance problems elsewhere. I shall take a look.)
The other way to work around the verifier's limits is to define
function tally
in a different way, in particular avoiding the "take"
operations, which the verifier does not want to expand a lot. It is
not clear what to change to make the verifier happy in these
situations, or if that's even possible at all, so this workaround may
not be the best. Nevertheless, I tried the following definition of
tally
and it makes your assertion go through:
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)
}
Note that these definitions do not use any "take" operations. Here, the verifier is happy to expand all the recursive calls until the final answer is found.
Rustan