| Rules | categories Tables, Rules and Hypothetical Reasoning Hypothetical Reasoning |
I/O, Modules and System Interface Communication ports |
In addition to rules, CLAIRE also provides the ability to do some hypothetical reasoning. It is indeed possible to make hypotheses on part of the knowledge (the database of relations) of CLAIRE, and to change them whenever we come to a dead-end. This possibility to store successive versions of the database and to come back to a previous one is called the world mechanism (each version is called a world). The slots or tables x on which hypothetical reasoning will be done need to be specified with the declaration store(x). For instance :
| store(age,friends,fib) <=> store(age), store(friends), store(fib) |
Definition : A world is a virtual copy of the defeasible part of the object database. The object database (set of slots, tables and global variables) is divided into the defeasible part and the stable part using the store declaration. Defeasible means that updates performed to a defeasible relation or variable can be undone later; r is defeasible if store(r) has been declared. Creating a world (choice) means storing the current status of the defeasible database (a delta-storage using the previous world as a reference). Returning to the previous world (backtrack) is just restoring the defeasible database to its previously stored state. |
In addition, you may accept the hypothetical changes that you made within a world while removing the world and keeping the changes. This is done with the commit methods. commit() decreases the world counter by one, while keeping the updates that were made in the current world. It can be seen as a collapse of the current world and the previous world. commit(n) repeats commit() until the current world is n. Notice that this "collapse" will simply make the updates that were made in the current world (n) look like they were made in the previous world (n - 1); thus, these updates are still defeasible.
Defeasible updates are fairly optimized in CLAIRE, with an emphasis on minimal book-keeping to ensure better performance. Roughly speaking, CLAIRE stores a pair of pointers for each defeasible update in the world stack. There are (rare) cases where it may be interesting to record more information to avoid overloading the trailing stack. For instance, trailing information is added to the stack for each update even if the current world has not changed. This strategy is actually faster than using a more sophisticated book-keeping, but may yield a world stack overflow.
For instance, here is a simple program that solves the n queens problem (the problem is the following: how many queens can one place on a chessboard so that none are in situation of chess, given that a queen can move vertically, horizontally and diagonally in both ways ?) :
| column[n:(1 .. 8)] : (1 .. 8) := unknown possible[x:(1 .. 8), y:(1 .. 8)] : boolean := true store(column, possible) r1() :: rule(column[x] := z => for y in ((1 .. 8) but x) possible[y,z] := false) r2() :: rule(column[x] := z => let d := x + z in for y in (max(1,d - 8) .. min(d - 1, 8)) possible[y,d - y] := false ) r3() :: rule(column[x] := z => let d := z - x in for y in (max(1,1 - d) .. min(8,8 - d)) possible[y,y + d] := false) queens(n:(0 .. 8)) : boolean -> (if (n = 0) true else exists(p in (1 .. 8) | (possible[n,p] & branch((column[n] := p, queens(n - 1)))))) (queens(8)) |
Note that the table possible, which tells us whether the nth queen can be set on the pth line, is filled by means of rules triggered by column (declared event) and that both possible and column are declared store so that the decisions taken in worlds that have been left are undone (this avoids to keep track of decisions taken under hypotheses that have been dismissed since).
Updates on lists can also be "stored" on worlds so that they become defeasible. Instead of using the nth= method, one can use the method store(l,x,v,b) that places the value v in l[x] and stores the update if b is true. In this case, a return to a previous world will restore the previous value of l[x]. If the boolean value is always true, the shorter form store(l,x,y) may be used. Here is a typical use of store :
| store(l,n,y,l[n] != y) |
| A[i:(1 .. 10)] : tuple(integer,integer,integer) := list<integer>(0,0,0) (let l := A[x] in (l[1] := 3, l[3] := 3)) |
| A[x] := list(3, A[x][2], 3) |
| (let l := A[x] in (store(l,1,3), store(l,3,3))) |
| (for i in (1 .. 10) A[i] := list<integer>(0,0,0)) |
It is also important to notice that the management of defeasible updates is done at the relation level and not the object level. Suppose that we have the following :
| C1 <: object(a:list<any>, b:integer) C2 <: thing(c:C1) store(c,a) P :: C1() P.c := C2(a = list<any>(1,2,3) , b = 0) // defeasible but the C2 object remains P.c.a := delete(copy(P.c.a), 2) // this is defeasible P.c.b := 2 // not defeasible |
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
backtrack() pops the current world and returns to the previous one.
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
backtrack(n) returns to the world numbered with n, and pops all the intermediary worlds.
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
choice() creates a new world and steps into it.
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
The method commit() returns to the previous world but carries the updates that were made within the current world; these updates now belong to the previous world and another call to backtrack() would undo them.
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
commit(n) returns to the world numbered with n through successive applications of commit().
| categories | Hypothetical Reasoning | normal dispatch | Core method |
contradiction!() throws a contradiction. contradiction!() uses a unique instance of the contradiction exception class such to save a little memory.
| categories | Hypothetical Reasoning | normal dispatch | [XL] Kernel method |
XL CLAIRE provides a convenient way to (pre)allocate defeasible lists in a class prototypes. Starting with CLAIRE 3, the content of a defeasible list have to be allocated prior to perform updates on it. prealloc_list(t,n) allocates an empty list for a class prototype that will automatically initialize a slot with a preallocated list of n elements and initially empty :
| lesson <: ephemeral_object( students:list[student] = prealloc_list(student, 100)) (store(students)) lesson!() : lesson -> lesson() |
| categories | Hypothetical Reasoning | normal dispatch | [XL] Kernel method |
preallocate a set in a similar way to prealloc_list.
| categories | Hypothetical Reasoning | normal dispatch | Core method |
put_store(r,x,v,b) is equivalent to put(r,x,v) but also stores this update in the current world if b is true. The difference with the use of the statement store(p) is that put_store allows the user to control precisely which update should be backtracked.
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
store(r1, r2, ...) declares the relations (properties or arrays) as defeasible (using the world mechanism)
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
store(v) declares the global variable v as defeasible (using the world mechanism).
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
store(a, n, v, b) is equivalent to a[n] := v but also stores this update in the current world if b is true. As a syntactical convenience, the argument b may be omitted if it is true. Note that there is a similar method for properties called put_store.
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
store(l, n, v, b) is equivalent to l[n] := v but also stores this update in the current world if b is true. As a syntactical convenience, the argument b may be omitted if it is true. Note that there is a similar method for properties called put_store.
| categories | Hypothetical Reasoning | normal dispatch | Kernel method |
world?() returns the index of the current world.