As we said previously, CLAIRE supports two syntaxes for using selectors, f(...) and
(.... f ....). The choice only exists when the associated methods have exactly two arguments.
The ability to be used with an infix syntax is attached to the property f :
Once f has been declared as an operation, CLAIRE will check that it is used as such
subsequently. Restrictions of f can then be defined with the usual syntax :
Note that declaring f as an operation can only be done when no restriction of f is known.
If the first appearance of f is in the declaration of a method, f is considered as a normal
selector and its status cannot be changed thereafter. Each operation is an object (inherits
from property) with a precedence slot that is used by the reader to produce the proper syntax
tree from expressions without parentheses.
gcd :: operation(precedence = precedence(/)) 12 + 3 gcd 4 // same as 12 + (3 gcd 4) |
So far we have assumed that any method definition is allowed, provided that inheritance
conflict may cause warning. Once a property is compiled, CLAIRE uses a more restrictive
approach since only new methods that have an empty intersection with existing methods (for
a given property) are allowed. This allows the compiler to generate efficient code. It is
possible to keep the "open" status of a property when it is compiled through the
abstract declaration.
Such a statement will force CLAIRE to consider f as an "abstract" parameter of the program
that can be changed at any time. In that case, any re-definition of f (any new method) will
be allowed. When defining a property parameter, one should keep in mind that another user
may redefine the behavior of the property freely in the future. It is sometimes useful to model a system with redundant information. This can be done by
considering pairs of relations inverse one of another. In this case the system maintains the
soundness of the database by propagating updates on one of the relations onto the other. For
example if husband is a relation from the class man onto the class woman and wife a relation
from woman to man, if moreover husband and wife have been declared inverse one of another,
each modification (addition or retrieval of information) on the relation husband will be
propagated onto wife. For example husband(mary) := john will automatically generate the update
wife(john) := mary. Syntactically, relations are declared inverses one of another with
the declaration :
This can be done for any relation: slots and tables. Inverses introduce an important distinction
between multi-valued relations and mono-valued relations. A relation is multi-valued in CLAIRE
when its range is a subset of bag (i.e. a set or a list). In that case the slot multivalued? of
the relation is set to true and the set associated with an object x is supposed to be the set
of values associated with x through the relation. This has the following impact on inversion. If r and s are two mono-valued relations inverse one
of another, we have the following equivalence :
In addition, the range of r needs to be included in the domain of s and conversely. The
meaning of inversion is different if r is multi-valued since the inverse declaration now means :
Two multi-valued relations can indeed be declared inverses one of another. For example, if
parents and children are two relations from person to set[person] and if inverse(children) =
parents, then :
| children(x) = {y in person | x % parents(y)} |
Modifications to the inverse relation are triggered by updates (with :=) and creations of
objects (with filled slots). Since the explicit inverse of a relation is activated only upon
modifications to the database (it is not retroactive), one should always set the declaration
of an inverse as soon as the relation itself is declared, before the relation is applied on
objects. This will ensure the soundness of the database. To escape the triggering of updates
to inverse relations, the solution is to fill the relation with the method put instead of :=.
For example, the following declaration :
| let john := person() in (put(wife,john,mary), john) |
does the same as :
| john :: person(wife = mary) |
without triggering the update husband(mary) := john.
inverse(r:relation) -> relation
r.inverse contains the inverse relation of r. If the range of r inherits from bag
then r is considered multi-valued by default. If r and its inverse are mono-valued
then if r(x) = y then inverse(r)(y) = x. If they are multi-valued, then inverse(r)(y)
returns the set (resp. list) of all x such that (y % r(x)).