digitalmars.D - More on invariants
- bearophile (196/196) Mar 17 2010 D is the first language that I've used that has contract programming, so...
D is the first language that I've used that has contract programming, so I've
studied some about this topic.
In a recent post I have discussed about some possible (silly) new parts for the
contract programming; this post is just about class/struct invariants and loop
invariants.
Some of the following things come from the nice slides:
http://www.cse.yorku.ca/course/3311/slides/08-Assertions.pdf
--------------------
Loop Invariant:
A loop invariant expresses a relation that is true when the loop is entered, or
entered once more.
I've found a small example in Eiffel:
Inserting an element into a sorted singly linked list
p = pointer to the current item
pp = pointer to the list item precedent to the item pointed by p
row := matElem.row
column := matElem.column
from
p := rowList row
invariant
predecessor_relation : (pp = void and p = rowList row) or
(pp /= void and pp.next = p)
predecessor_before_column: pp = void or pp.column < column
-- forall k : rowList row .. pp :: k.column < column
data_less_than : column_limit(rowList row, pp, agent less_than(?, column))
until
p = void or p.column >= column
loop
pp := p
p := p.next
end
I have translated it to D2 (I have removed the semantics of matrix, and added
driving code to show it works, I have not written unittests here):
import std.stdio: write, writeln;
struct Node {
immutable int data; // the cargo can't be changed
Node* next;
this(const int d, Node* next_ptr=null) {
data = d;
next = next_ptr;
}
}
void printList(const(Node)* p) {
write("List: ");
for (; p != null; p = p.next)
write(p.data, " ");
writeln();
}
void prependNode(ref Node* list_header, const int d)
// list_header is a reference of a mutable pointer that's
// the header pointer of a const list
out {
assert(list_header != null);
} body {
Node* ptr = new Node(d, list_header);
list_header = ptr;
}
void insertSorted(ref Node* list_header, Node* nptr)
// nptr is a const pointer to a mutable node,
// I don't know how to express this
in {
assert(nptr != null);
assert(list_header != nptr);
//assert(nptr.next == null); // probably too much restrictive
// the list is sorted
debug { // this is slow, so it's inside a debug
if (list_header != nptr) {
Node* p = list_header; // pointer to the current Node
Node* pp; // pointer to the precedent Node
while (p != null) {
if (pp != null)
assert(pp.data <= p.data);
pp = p;
p = p.next;
}
}
}
} out {
assert(list_header != null);
// the list is sorted and contains the given node
debug {
Node* p = list_header; // pointer to the current Node
Node* pp; // pointer to the precedent Node
while (p != null) {
if (pp != null)
assert(pp.data <= p.data);
pp = p;
p = p.next;
}
}
} body {
Node* p = list_header; // pointer to the current Node
Node* pp; // pointer to the precedent Node
while (p != null && p.data < nptr.data) {
/*invariant*/ { // loop invariant --------------------
// predecessor relation
assert((pp == null && p == list_header) || (pp != null &&
pp.next == p));
// predecessor before data
assert(pp == null || pp.data < nptr.data);
// current before data
assert(p == null || p.data < nptr.data); // extra
// data less than
// forall k : rowList row .. pp :: k.column < column
debug { // slow
if (pp != null) {
Node* pk = list_header;
while (pk != pp) {
assert(pk.data < nptr.data);
pk = pk.next;
}
}
}
} // end loop invariant --------------------
pp = p;
p = p.next;
}
assert((pp == null && p == list_header) || (pp != null && pp.next ==
p));
assert(pp == null || pp.data < nptr.data);
if (pp != null)
pp.next = nptr;
else
list_header = nptr;
nptr.next = p;
}
void main() {
Node* lh;
foreach (x; [20, 15, 12, 11, 11, 9, 3, 0])
prependNode(lh, x);
printList(lh); // 0 3 9 11 11 12 15 20
insertSorted(lh, new Node(13));
printList(lh); // 0 3 9 11 11 12 13 15 20
insertSorted(lh, new Node(-1));
printList(lh); // -1 0 3 9 11 11 12 13 15 20
insertSorted(lh, new Node(30));
printList(lh); // -1 0 3 9 11 11 12 13 15 20 30
}
I have added those debug{} because they are heavy tests that change the
complexity of the code.
That code inside the debug{} is too much long and it looks like it can contain
more bugs than the code it guards.
A loop invariant syntax can be useful in D because:
- It self-documents its purpose;
- You can't forget to create a new scope, this is a little safer;
- In future it can be enforced to be readonly (that is, the programmer can be
sure the code inside the invariant can't modify the variabiles in outside the
scope of the invariant, but can read them);
- There can be only one loop invariant, and it must be before the body of the
loop. This helps better separate the asserts from the body of the loop, and
keeps things tidy.
A possible syntax for D2 for loop invariant, no new keywords necessary, and the
syntax is essentially a cross between precodition and class invariant:
while (...)
invariant { } // only allowed at the top of the loop, only 1 optional invariant
body {} // body of the loop
for (...)
invariant { }
body {}
Using that syntax the loop becomes:
while (p != null && p.data < nptr.data)
invariant {
// predecessor relation
assert((pp == null && p == list_header) || (pp != null && pp.next ==
p));
// predecessor before data
assert(pp == null || pp.data < nptr.data);
// current before data
assert(p == null || p.data < nptr.data); // extra
//...
} body {
pp = p;
p = p.next;
}
Note: this loop invariant syntax is an additive change, it can't break older D2
code, so it can be added later.
Note2: While reading Eiffel docs I have read: "Use comments if you cannot write
an executable assertion". So it says that in some situations inside the
conditions/invariants it's better to have a comment readable by just the
programmer, than nothing.
------------------
Class Invariant:
This is a short example, a class written in what I think is Eiffel-like
pseudolanguage, it shows some things/features:
class CITIZEN feature
name, sex, age : VALUE
spouse : CITIZEN
children, parents : SET[CITIZEN]
single : BOOLEAN is ensure Result iff (spouse = Void) end
divorce is
require not single
ensure single and (old spouse).single
end
invariant
single or spouse.spouse = Current
parents.count = 2
for_all c member_of children it_holds
(exists p member_of c.parents it_holds p = Current)
end
With some care all this can be written in with class invariant in D too.
--------------------
Things missing in D Contract programming:
Very useful and probably easy to do:
- class invariant has to be const (readonly instance).
- pre/post conditions can't be able to modify input arguments.
Useful:
- pre/post conditions can't be able to modify variables of outer/global scope
( readonly).
- no loop invariant.
- compact syntax (*)
Less useful:
- no loop variant.
(*) This comes from my experiments and from reading Eiffel docs. The code
inside the conditions/invariants has to be short and readable. So it's better
it to be quite high-level. Python is very good in this, it has list comps and
other things that shorten the code a lot.
In Eiffel you can't put generic code in conditions and invariants, probably to
keep them short and almost bug-free. If you want to perform more complex tests
Eiffel allows you to call another function from there, that can be debugged
normally. So in that D example code it's better to move the debug code to
external functions.
Bye,
bearophile
Mar 17 2010








bearophile <bearophileHUGS lycos.com>