Below is a first attempt to prove various simple theorems, in this case about parity. Dafny /v. 1.9.9.40414/ verifies that adding 2 to an even number yields an even number but does not accept either of the commented out conditions.
function IsEven(a : int) : bool
requires a >= 0
{
if a == 0 then true
else if a == 1 then false
else IsEven(a - 2)
}
method Check1(a : int)
requires a >= 0
ensures IsEven(a) ==> IsEven(a + 2)
//ensures IsEven(a) ==> IsEven(a + a)
//ensures IsEven(a) ==> IsEven(a * a)
{
}
As I have just started to study this wonderful tool, my approach or the implementation might be incorrect. Any advice would be appreciated.
There are few different things going on here. I will discuss each of the three postconditions in turn.
The first and second postconditions
Since IsEven is a recursively defined predicate, in general, facts about it will require proofs by induction. Your first post condition is simple enough to not require induction, which is why it goes through.
Your second postcondition does require induction to be proved. Dafny has heuristics for automatically performing induction, but these heuristics are only invoked in certain contexts. In particular, Dafny will only attempt induction on "ghost methods" (also called "lemmas").
If you add the keyword ghost in front of method in Check1 (or change method to lemma, which is equivalent), you will see that the second postcondition goes through. This is because Dafny's induction heuristic gets invoked and manages to complete the proof.
The third postcondition
The third postcondition is more complex, because it involves nonlinear arithmetic. (In other words, it involves nontrivial reasoning about multiplying two variables together.) Dafny's underlying solver has trouble reasoning about such things, and so the heuristic proof by induction doesn't go through.
A proof that a * a is even if a is even
One way to prove it is here. I have factored out IsEven(a) ==> IsEven(a * a) into its own lemma, called EvenSquare. I have also changed it to require IsEven(a) as a precondition, rather than put an implication in the postcondition. (A similar proof also goes through with the implication instead, but using preconditions on lemmas like this instead of implications is idiomatic Dafny.)
The proof of EvenSquare is by (manual) induction on a. The base case is handled automatically. In the inductive case (the body of the if statement), I invoke the induction hypothesis (ie, I make a recursive method call to EvenSquare to establish that (a - 2) * (a - 2) is even). I then assert that a * a can be written as the sum of (a - 2) * (a - 2) and some offset. The assertion is dispatched automatically. The proof will be done if I can show that the right hand side of this equality is even.
To do this, I already know that (a - 2) * (a - 2) is even, so I first invoke another lemma to show that the offset is even, because it is twice something else. Finally, I invoke one last lemma to show that the sum of two even numbers is even.
This completes the proof, assuming the two lemmas.
Proofs of the two lemmas
It remains to show that twice anything is even, and that the sum of two even numbers is even. While not completely trivial, neither is as complex as EvenSquare.
The lemma EvenDouble proves that twice anything is even. (This is in fact a stronger version of your second postcondition. Your second postcondition says that doubling any even number is even. In fact, doubling any (non-negative, under your definition of evenness) number at all is even.) The proof of EvenDouble proceeds by (manual) induction on a. The base case is handled automatically. The inductive case only requires explicitly invoking the induction hypothesis.
The lemma EvenPlus is almost proved automatically by Dafny's induction heuristic, except that it trips over a bug or some other problem which causes a loop in the solver. After a little debugging, I determined that the annotation {:induction x} (or {:induction y}, for that matter) makes the proof not loop. These annotations tell Dafny's heuristics which variable(s) to try to induct on. By default in this case, Dafny tries to induct on both x and y, which for some reason causes the solver to loop. But inducting on either variable alone works. I'm investigating this problem further, but the current solution works.
Related
I'm trying to figure something out in dafny.
Given 2 arrays a and b, my assertions, invariants, post conditions, etc in the form of:
multiset(a[..]) == multiset(b[..]);
fails but
multiset(a[..a.Length]) == multiset(b[..b.Length])
succeeds.
I'm very confused by this because I assumed a[..] and a[..a.Length] would be the exact same thing. However, I found something interesting. If I add at the end of my method:
assert a[..a.Length] == a[..];
assert b[..b.Length] == b[..];
then I can get the invariants, post conditions, assertions involving my first example to work.
This suggests to me that a[..] and a[..a.Length] are actually different.
Could someone please explain why this is the case and what is happening here?
You are correct that a[..] and [..a.Length] (and, for that matter, also a[0..] and a[0..a.Length]) are the same thing. However, it may be that verifier treats these slightly differently. This makes a difference because the lack of (caution: technical word coming up) extensionality in Dafny.
Extensionality means that, if you know two things have the same elements, then they are the same thing. In your example, extensionality would mean that, if you know a[..] and a[..a.Length] to have the same elements, then a[..] and a[..a.Length] are the same thing.
The lack of extensionality in Dafny means that the verifier sometimes knows that two things have the same elements, but it still doesn't draw the conclusion that the two things are the same. This tends to be noticeable when the two things are passed to a function. In your example, that function is multiset(...), which converts a sequence into a multiset.
While Dafny does not support extensionality automatically, it does offer a simple technique to "remind" the verifier about extensionality. The technique is to assert the equality between the two things. In particular, when you write assert A == B; for two sequences A and B, then the verifier will first prove that A and B have the same elements, and then it will draw the conclusion that A is in fact equal to B. In other words, when the verifier is explicitly asked to verify the equality of two sequence-valued expressions like A and B, then it instead just proves that they are element-wise equal, after which it concludes A == B.
So, your remedy above is exactly the right thing to do. When you assert the equality between a[..a.Length] and a[..], the verifier proves that they have the same elements. After that, it "remembers" that this also means that a[..a.Length] and a[..] are equal. Once it has realized the two sequences are the same, it immediately also knows that functions of them, like multiset(a[..a.Length]) and multiset(a[..]), are the same.
More generally, extensionality is relevant for not just sequences, but also for sets, multisets, and maps. So, if you're working with any of those collection types, you may need to write assertions about sequence equality, set equality, etc., to "remind" the verifier about extensionality.
More generally, there are many things that are true and that the verifier doesn't immediately verify. To home in on what may be missing, the common technique is to start breaking down the proof obligations yourself, like you did in the assert statements. By breaking down more complicated proof obligations into simpler ones (which is usually done with assert statements or calc statements), you essentially provide hints to the verifier about how to prove the more complicated things.
This question is a continuation of the comments at Using Local Special Variables, regarding how best to avoid global variables. As I understand it, global variables are problematic mainly because they have the potential to interfere with referential transparency. Transparency is violated if an expression changes a global value using information outside its calling context (eg, a previous value of the global variable itself, or any other external values). In these cases evaluating the expression may have different results at different times, either in the value(s) returned or in side effects. (However, it seems not all global updates are problematic, since some updates may not depend on any external information--eg, resetting a global counter to 0). The normal global approach for a deeply embedded counter might look like:
* (defparameter *x* 0)
*X*
* (defun foo ()
(incf *x*))
FOO
* (defun bar ()
(foo))
BAR
* (bar)
1
* *x*
1
This would seem to violate referential transparency because (incf *x*) depends on the external (global) value of *x* to do its work. The following is an attempt to maintain both functionality and referential transparency by eliminating the global variable, but I'm not convinced that it really does:
* (let ((x 0))
(defun inc-x () (incf x))
(defun reset-x () (setf x 0))
(defun get-x () x))
GET-X
* (defun bar ()
(inc-x))
BAR
* (defun foo ()
(bar))
FOO
* (get-x)
0
* (foo)
1
* (get-x)
1
The global variable is now gone, but it still seems like the expression (inc-x) has a (latent) side effect, and it will return different (but unused) values each time it is called. Does this confirm that using a closure on the variable in question does not solve the transparency problem?
global variables are problematic mainly because they have the potential to interfere with referential transparency
If one wants to create a global configuration value, a global variable in Common Lisp is just fine.
Often it's desirable to package a bunch of configuration state and then it may be better to put that into an object.
There is no general requirement for procedures to be referential transparent.
It's useful to guide software design by software engineering principles, but often easy debugging and maintenance is more important than strict principles.
(let ((x 0))
(defun inc-x () (incf x))
(defun reset-x () (setf x 0))
(defun get-x () x))
Practically above means that it
is difficult to inspect
has problematic effects of reloading the code
prohibits the file compiler to recognize the top-level nature of the functions
creates a whole API for just managing a single variable
Referential transparency means that if you bind some variable x to an expression e, you can replace all occurrences of x by e without changing the outcome. For example:
(let ((e (* pi 2)))
(list (cos e) (sin e)))
The above could be written:
(list (cos (* pi 2))
(sin (* pi 2)))
The resulting value is equivalent to the first one for some useful definition of equivalence (here equalp, but you could choose another one). Contrast this with:
(let ((e (random))
(list e e))
Here above, each call to random gives a different result (statistically), and thus the behaviour is different if you reuse the same result multiple times or generate a new after each call.
Special variables are like additional arguments to functions, they can influence the outcome of a result simply by being bound to different values. Consider *default-pathname-defaults*, which is used to build pathnames.
In fact, for a given binding of that variable, each call to (merge-pathnames "foo") returns the same result. The result changes only if you use the same expression in different dynamical context, which is no different than calling a function with different arguments.
The main difficulty is that the special variable is hidden, i.e. you might not know that it influences some expressions, and that's why you need them documented and limited in number.
What breaks referential transparency is the presence of side-effects, whether you are using lexical or special variables. In both cases, a place is modified as part of the execution of the function, which means that you need to consider when and how often you call it.
You could have better suggestions if you explained a bit more how your code is organized. You said that you have many special variables due to prototyping but in the refactoring you want to do it seems as-if you want to keep to prototypal code mostly untouched. Maybe there is a way to pack things in a nice modular way but we can't help without knowing more about why you need many special variables, etc.
That code isn't referentially transparent. It is an improvement from special variables though.
The code you put would be a functional nonce if you dropped the reset-x.
My answer to your previous question had general guidelines about special variables. For your specific case, perhaps they are worth it? I could see the case for using special variables as a nonce, for example, where it is probably silly to pass them around.
Common Lisp has so many facilities for dealing with global information, so there is rarely a need for having lots of global variables. You could define an *env* alist to store your values in, or put them in a hash table, or put them into symbol plists, or package them in a closure to pass around, or do something else, or use CLOS.
Where is the side effect of the second example ? The x inside the let isn't accessible from the outside.
Here's another closure example, with top-level functions, and a counter explicitly inside it.
(defun repeater (n)
(let ((counter -1))
(lambda ()
(if (< counter n)
(incf counter)
(setf counter 0)))))
(defparameter *my-repeater* (repeater 3))
;; *MY-REPEATER*
(funcall *my-repeater*)
0
(funcall *my-repeater*)
1
https://lispcookbook.github.io/cl-cookbook/functions.html#closures
In a (rather large) Z3 problem, we have a few axioms of the shape:
forall xs :: ( (P(xs) ==> (exists ys :: Q(xs,ys))) && ((exists zs :: Q(xs,zs)) ==> P(xs)) )
All three quantifiers (including the existentials) have explicit triggers provided (omitted here). When running the problem and gathering quantifier statistics, we observed the following data (amongst many other instantiations):
[quantifier_instances] k!244 : 804 : 3 : 4
[quantifier_instances] k!232 : 10760 : 29 : 30
Here, line 244 corresponds to the end of the outer forall quantifier, and line 232 to the end of the first inner exists. Furthermore, there are no reported instantiations of the second inner exists (which I believe Z3 will pull out into a forall); given the triggers this is surprising.
My understanding is that existentials in this inner position should be skolemised by a function (depending on the outer quantifier). It's not clear to me what quantifier statistics mean for such existentials.
Here are my specific questions:
Are quantifier statistics meaningful for existential quantifiers (those which remain as existentials - i.e. in positive positions)? If so, what do they mean?
Does skolemisation of such an existential happen once and for all, or each time the outer quantifier is instantiated? Why is a substantially higher number reported for this quantifier than for the outer forall?
Does Z3 apply some internal rewriting of this kind of (A==>B)&&(B==>A) assertion? If so, how does that affect quantifier statistics for quantifiers in A and B?
From our point of view, understanding question 2 is most urgent, since we are trying to investigate how the generated existentials affect the performance of the overall problem.
The original smt file is available here:
https://gist.github.com/anonymous/16e489ce5c513e8c4bc6
and a summary of the statistics generated (with Z3 4.4.0, but we observed the same with 4.3.2) is here:
https://gist.github.com/anonymous/ce7b96acf712ac16299e
The answer to all these questions is 'it depends', mainly on what else appears in the problem and what options are set. For instance, if there are only bit-vector variables, then Skolemization will indeed be performed during preprocessing, once and forall, but this is not the case for all other theories or theory combinations.
Briefly looking at your SMT2 file, it seems to me that all existentials appear in the left hand side of implications, i.e., they are in fact negated (and actually rewritten into universals somewhere along the line), so those statistics do make sense for the existentials appearing in this particular problem.
I'm starting to doubt I really understand this topic.
Until now, I was understanding a continuation as calling a function with closure (typically returned by another function). But MLton seems to have a non‑standard special structure for this (a structure I'm not sure to understand), and also in some other documents, mention special optimizations (using jumps, as quickly mentioned on page 58, printed page 51) with continuations, namely, instead of naming call to functions with closure. Also, function closures seems to be sometime described as the basis for continuations, but not described as being continuations, while some other times people assert the opposite (that function closures are special case of continuations, not the other way).
As an example, how do continuations differs from this, and what would looks like the same, with continuations instead of function with closure:
datatype next = Next of (unit -> next)
fun f (i:int): next =
(print (Int.toString i);
Next (fn () => f (i + 1)))
val Next g = f 1
val Next g = g ()
val Next g = g ()
val Next g = g ()
…
I wonder about it, in the general computer‑science context, as much as specifically in the practical SML context.
Note: the question may looks the same as “difference between closures and continuations”, but reading this one did not answer my question and does not address a practical case as a basis. Except it drove me to add another question: why are continuations said to be more abstract than closures, if in the end continuations are made of closures as the incomplete (to my eyes) answer in the above link suggest?
Is the difference really important or just a matter of style / syntax / vocabulary?
I feel a similar question arise with monads versus continuations, but that would be too much for a single question post (but if on the opposite, that can be simply answered in the while, feel free…).
Update
Still from MLton's world, a wording which seems to suggest continuations and function closures are the same (unless I'm not understanding correctly).
CommonArg (mlton.org), near the bottom of the page, says:
What I think the common argument optimization shows is that the
dominator analysis does slightly better than the reviewer puts it:
we find more than just constant continuations, we find common
continuations. And I think this is further justified by the fact
that I have observed common argument eliminate some env_X arguments
which would appear to correspond to determining that while the
closure being executed isn’t constant it is at least the same as
the closure being passed elsewhere.
It's talking about the same using both words, isn't it?
Similarly and may be more explicitely, at the bottom on this page: ReturnStatement (mlton.org).
There too, it seems to be the same. Is it?
It seems there is a terminological confusion. 'Continuation' is an abstract concept, which is a meaning of a context of an expression. Closure is a very particular way to realize
values that represent functions (higher-order languages can be implemented without closures at all, for example, using substitution semantics).
Control operator can capture the current continuation and produce a particular representation of it (this is called reification). The particular representation of a captured continuation may indeed be a closure -- or may be not. For example, in OCaml, the continuations captured by the delimcc library are repersented as values of the abstract data type (whose realization is quite different from closures). You might find the introduction part of the following page useful.
Undelimited continuations are not functions
When clauses in function and match statements aren't mutually exclusive, the order clearly matters. However when clauses are mutually exclusive they can be written in any order. E.g., to find the minimum element in a list, the following are functionally equivalent:
let rec minElt =
function
| [] -> failwith "empty list"
| [x0] -> x0
| x0::xtl -> min x0 (minElt xtl)
let rec minElt =
function
| [x0] -> x0
| x0::xtl -> min x0 (minElt xtl)
| [] -> failwith "empty list"
I prefer the first one stylistically, because the patterns are listed in increasing order of size / the base cases are first. However is there any advantage for the second one? In particular, is the second one more efficient because the exceptional case will never be checked in the course of normal evaluation?
I don't think there is any established idiomatic style. I would focus on making the code readable and understandable first - I think this depends on personal preferences, but I guess you could write:
Special cases first (anything that needs special handling, or handles special but valid values)
Most common cases next (the typical path, such as x::xs for lists)
Exceptional cases (anything that means invalid input)
I guess this is how I generally tend to write pattern matching (because this is the order in which I think about the possible cases).
I would not worry too much about the performance. I tested your function just out of curiosity. I called it 1000 times on lists from length 1 to 100 (so that's 100000 iterations) and the first one was about 895ms while the second one 878ms so the difference is 2%. Does not sound like something that would matter over readability (this was in F# Interactive, so the difference might be even smaller).
I typically put the finishing case of a recursive function first. This is usually either the 0 case or the 1 case. Next I put the case which recurses (assuming there is only one), followed by any exceptional cases.
The reasoning behind this is that it's how I understand inductive reasoning: i.e. I understand the base case (how the recursion ends), following by how that base case is used to prove the correctness of the algorithm (how the recursion reaches the end). I put exceptional cases last because they don't add to my understanding of the logic behind the recursion. Recursion is more difficult to understand if you start in the middle of the argument than if you start at the end (and since recursion doesn't have a well-defined beginning point, you can't very well start there).
This reasoning leans heavily on the mathematical foundations of functional programming, so if that isn't how you approach functional programming, then maybe some other ordering would make more sense to you.