Methods and Types

4.1 Methods

A method is the definition of a property for a given signature. A method is defined by the following pattern :

· a selector (the name of the property represented by the method),

· a list of typed parameters (the list of their types forms the domain of the method),

· a range expression and

· a body (an expression or a let statement introduced by -> or =>).

<selector>(<typed parameters>) : <range>opt ->|=> <body>

fact(n:{0}) : integer -> 1

fact(n:integer) : integer -> (n * fact(n - 1))

print_test() : void -> print("Hello"), print("world\n")

Definition: A signature is a Cartesian product of types that always contains the extension of the function. More precisely, a signature A1 X A2 X ... X An, also represented as list(A1,...,An) or A1 X A2 X ... X An-1 -> An, is associated to a method definition f(...) : An ® ... for two purposes: it says that the definition of the property f is only valid for input arguments (x1, x2, ..., xn-1) in A1 ´ A2 ´ ... ´ An-1 and it says that the result of f(x1, x2, ..., xn-1) must belong to An. The property f is also called an overloaded function and a method m is called its restriction to A1 X A2 X ... X An-1.

If two methods have intersecting signatures and the property is called with an argument list (list of objects) that belongs to both signatures, the definition of the method with the smaller domain is taken into account. If the two domains have a non-empty intersection but are not comparable, a warning is issued and the result is implementation-dependent. The set of methods that apply for a given class or return results in another can be found conveniently with methods.

methods(integer,string) ;; returns {date!@integer, string!@integer, make_string@integer}

The range declaration can only be omitted if the range is void. In particular, this is convenient when using the interpreter.

loadMM() -> (begin(my_module), load("f1"), load("f2"), end(my_module))

If the range is void (unspecified), the result cannot be used inside another expression (a type-checking error will be detected at compilation). A method’s range must be declared void if it does not return a value (for instance, if its last statement is, recursively, a call to another method with range void). It is important not to mix restrictions with void range with other regular methods that do return a value, since the compiler will generate an error when compiling a call unless it can guarantee that the void methods will not be used.

The default range was changed to void in the version 3.3 of CLAIRE, in an effort to encourage proper typing of methods: “no range” means that the method does not return a value. This is an important change when migrating code from earlier versions of CLAIRE.

claire supports methods with a variable number of arguments using the listargs keyword. The arguments are put in a list, which is passed to the (unique) argument of type listarg. For instance, if we define

[f(x:integer,y:listargs) -> x + size(y)]

A call f(1,2,3,4) will produce the binding x = 1 and y = list(2,3,4) and will return 4.

CLAIRE also supports functions that return multiple values using tuples. If you need a function that returns n values v1,v2,…,vn of respective types t1,t2,…,tn, you simply declare its range as tuple(t1,t2,…,tn) and return tuple(v1,v2,…,vn) in the body of the function. For instance the following method returns the maximum value of a list and the “regret” which is the difference between the best and the second-best value.

[max2(l:list[integer]) : tuple(integer,integer)

-> let x1 := 1000000000, x2 := 1000000000 in

(for y in l

(if (y < x1) (x2 := x1, x1 := y) else if (y < x2) x2 := y),

tuple(x1,x2)) ]

The tuple produced by a tuple-valued method can be used in any way, but the preferred way is to use a tuple-assignment in a let. For instance, here is how we would use the max2 method:

let (a,b) := max2(list{f(i) | i in (1 .. 10)}) in …

The body of a method is either a claire expression (the most common case) or an external (C++) function. In the first case, the method can be seen as defined by a lambda abstraction. This lambda can be created directly through the following:

lambda[(<typed parameters>), <body> ]

Defining a method with an external function is the standard way to import a C/C++ function in claire. This is done with the function!(...) constructor, as in the following.

f(x:integer,y:integer) -> function!(my_version_of_f)

cos(x:float) -> function!(cos_for_claire)

The integration of external functions is detailed in Appendix C of the CLAIRE documentation. It is important to notice that in CLAIRE, methods can have at most 12 parameters. Methods with 40 or more parameters that exist in some C++ libraries are very hard to maintain. It is advised to use parameter objects in this situation.

CLAIRE also provides inline methods, that are defined using the => keyword before the body instead of ->. An inline method behaves exactly like a regular method. The only difference is that the compiler will use in-line substitution in its generated code instead of a function call when it seems more appropriate[1]. Inline methods can be seen as polymorphic macros and are quite powerful because of the combination of parametric function calls (using call(...)) and parametric iteration (using for). Let us consider the two following examples, where subtype[integer] is the type of everything that represents a set of integers:

sum(s:subtype[integer]) : integer => let x := 0 in (for y in s x :+ y, x)

min(s:subtype[integer], f:property) : integer

=> let x := 0, empty := true in

(for y in s

(if empty (x := y, empty := false)

else if call(f,y,x) x := y),

x)

For each call to these methods, the compiler performs the substitution and optimizes the result. For instance, the optimized code generated for sum({x.age | x in person}) and for min({x in 1 .. 10 | f(x) > 0}, >) will be

let x := 0 in

(for %v in person.instances

let y := %v.age in x :+ y, x)

let x := 0, empty := true, y := 1, max := 10 in

(while (y <= max)

(if (f(y) > 0)

(if empty (x := y, empty := false)

else if (y > x) x := y),

y :+ 1),

x)

Notice that, in these two cases, the construction of temporary sets is totally avoided. The combined use of inline methods and functional parameters provides an easy way to produce generic algorithms that can be instantiated as follows.

mymin(l:list[integer]) : integer -> min(l, my_order)

The code generated for the definition of mymin @ list[integer] will use a direct call to my_order (with static binding) and the efficient iteration pattern for lists, because min is an inline method. In that case, the previous definition of min may be seen as a pattern of algorithms.

CAVEAT: A recursive macro will cause an endless loop that may be painful to detect and debug.

For upward compatibility reasons (from release 1.0), claire still supports the use of external brackets around method definitions. The brackets are there to represent boxes around methods (and are pretty-printed as such with advanced printing tools). For instance, one can write:

[ mymin(l:list[integer]) : integer -> min(l, my_order) ]

Brackets have been found useful by some users because one can search for the definition of the method m by looking for occurrences of « [m ». They also transform a method definition into a closed syntactical unit that may be easier to manipulate (e.g., cut-and-paste).

When a new property is created, it is most often implicitly with the definition of a new method or a new slot, although a direct instantiation is possible. Each property has an extensibility status that may be one of:

· open, which means that new restrictions may be added at any time. The compiler will generate the proper code so that extensibility is guaranteed.

· undefined, which is the default status under the interpreter, means that the status may evolve to open or to closed in the future.

· closed, which means that no new restriction may be added if it provokes an inheritance conflict with an existing restriction. An inheritance conflict in CLAIRE is properly defined by the non-empty intersection of the two domains (Cartesian products) of the methods.

The compiler will automatically change the status from undefined to closed, unless the status is forced with the abstract declaration:

abstract(p)

Conversely, the final declaration:

final(p)

may be used to force the status to closed, in the interpreted mode. Note that these two declarations have obviously an impact on performance: an open property will be compiled with the systematic used of dynamic calls, which ensures the extensibility of the compiled code, but at a price. On the contrary, a final property will enable the compiler to use as much static binding as possible, yielding faster call executions. Notice that the interface(p) declaration has been introduced (cf. Appendix) to support dynamic dispatch in a efficient manner, as long as the property is uniform.



[1] The condition for substitution is implementation-dependent. For instance, the compiler checks that the expression that is substituted to the input parameter is simple (no side-effects and a few machine instructions) or that there is only one occurrence of the parameter.


4.2 Lambdas

Lambdas are name-less functions that can created dynamically. Lambdas are inherited from LISP and may be created with the “classical” CLAIRE syntax

lambda[(x:any), x]

lambda[(x:integer,y:integer), (x + y)]

lambda[(f:lambda,g:lambda), lambda[(x:any), funcall(f,funcall(g,x))]]

In CLAIRE4, we have introduced a simpler syntax : (v*){e*}. The variables v may be typed, otherwise the type is assumed to be any

(x){x}

(x:ingteger,y:integer){x + y}

(f:lambda,g:lambda){(x){funcall(f,funcall(g,x))}


Lambdas may be used with a few classical operators. The main pattern of functional programming language is to pass lambdas as parameters but remember that you can pass a method or a property as a parameter as well in CLAIRE.

funcall((x){x * x}),12) // 144

apply((x:ingteger,y:integer){x + y},list(1,2)) // 3

map((x){x * x},{1,2,4}) // {1,4,16}

4.3 Types

claire uses an extended type system that is built on top of the set of classes. Like a class, a type denotes a set of objects, but it can be much more precise than a class. Since methods are attached to types (by their signature), this allows attaching methods to complex sets of objects.

Definition: A (data) type is an expression that represents a set of objects. Types offer a finer-granularity partition of the object world than classes. They are used to describe objects (range of slots), variables and methods (through their signatures). An object that belongs to a type will always belong to the set represented by the type.

Any class (even parameterized) is a type. A parameterized class type is obtained by filtering a subset of the class parameters with other types to which the parameters must belong. For instance, we saw previously that complex[im:{0.0}] is a parametrized type that represent the real number subset of the complex number class. This also applies to typed lists or sets which use the of parameter. For instance, list[of:{integer}] is the set of list whose of parameter is precisely integer. Since these are common patterns, claire offers two shortcuts for parameterized type expressions. First, it accepts the expression C[p = v] as a shortcut for C[p:{v}]. Second, it accepts the expression C<X> as a shortcut for C[of = X]. This applies to any class with a type-valued parameter named of; for instance, the stack class defined in Section 2.3. Thus, stack<integer> is the set of stacks whose parameter "of" is exactly integer, whereas stack[of:subtype[integer]] is the set of stacks whose parameter (a type) is a subset of integer.

Finite constant sets of objects can also be used as types. For example, {john, jack, mary} and {1,4,9} are types. Intervals can be used as types; the only kind of intervals supported by CLAIRE 3.0 is integer intervals. Types may also formed using the two intersection (^) and union(U) operations. For example, integer U float denotes the set of numbers and (1 .. 100) ^ (-2 .. 5) denotes the intersection of both integer intervals, i.e. (1 .. 5).

Subtypes are also as type expressions. First, because types are also objects, CLAIRE introduces subtype[t] to represent the set of all type expressions that are included in t. This type can be intersected with any other type, but there are two cases which are more useful than other, namely subtypes of the list and set classes. Thus, CLAIRE uses set[t] as a shortcut for set ^ subtype[t] and list[t] as a shortcut for list ^ subtype[t]. Because of the semantics of lists, one may see that list[t] is the union of two kinds of lists:

(a) “read-only” lists (i.e., without type) that contains objects of type t,

(b) typed list from list<X>, where X is a subtype of t.

Therefore, there is a clear difference between

· list<t>, which only contains types lists, whose type parameter (of) must be exactly t.

· list[t], which contains both typed lists and un-typed lists.

Obviously, we have list<t> <= list[t].When should you use one or the other form of typed lists or sets ?

(1) use list[t] to type lists that will only be used by accessing their content. A method that uses l:list[t] in its signature will be polymorphic, but updates on l will rely on dynamic (run-time) typing.

(2) use list<t> to type lists that need to be updated. A method that uses l:list<t> in its signature will be monomorphic (i.e., will not work for l:list<t’> with t’ <= t), but updates will be statically type-checked (at compile time).

Last, CLAIRE uses tuple and array types. The array type t[] represents arrays whose member type is t (i.e., all members of the array belong to t). Tuples are used to represent type of tuples in a very simple manner: tuple(t1,t2,…,tn) represents the set of tuples tuple(v1,v2,…,vn) such that vi Î ti for all i in (1 .. n). For instance, tuple(integer, char) denotes the set of pair tuples with an integer as first element and a character as second. Also you will notice that tuple(class,any,type) belongs to itself, since class is a class and type is a type.


To summarize, here is the syntax for types expressions in CLAIRE v3.0 :

<type> :: <class> | <class>[<parameter>:<type>seq] | {<item>seq } |

(<integer> .. <integer>) |(<type> U <type>) | (<type> ^ <type>) |

set[<type>] | list[<type>] | <type>[] | subtype[<type>] |

tuple(<type>seq)

Classes are sorted with the inheritance order. This order can be extended to types with the same intuitive meaning that a type t1 is a subtype of a type t2 if the set represented by t1 is a subset of that represented by t2. The relation "t1 is a subtype of a type t2" is noted t1 <= t2. This order supports the introduction of the “ subtype ” constructor: subtype[t] is the type of all types that are less than t.


4.4 Polymorphism

In addition to the traditional "objet-oriented" polymorphism, claire also offers two forms of parametric polymorphism, which can be skipped by a novice reader.

(1)There often exists a relation between the types of the arguments of a method. Capturing such a relation is made possible in claire through the notion of an "extended signature". For instance, if we want to define the operation "push" on a stack, we would like to check that the argument y that is being pushed on the stack s belongs to the type of(s), that we know to be a parameter of s. The value of this parameter can be introduced as a variable and reused for the typing of the remaining variables (or the range) as follows.

push(s:stack<X>, y:X) -> ( s.content :add y, s.index :+ 1)

The declaration s:stack<X> introduced X as a type variable with value s.of, since stack[of] was defined as a parameterized class. Using X in y:X simply means that y must belong to the type s.of. Such intermediate type variables must be free identifiers (the symbol is not used as the name of an object) and must be introduced with the following template:

<class>[pi=vi,...,]

The use of type variables in the signature can be compared to pattern matching. The first step is to bind the type variable. If (p = V) is used in c[ ...] instead of p:t, it means that we do not put any restriction on the parameter p but that we want to bind its value to V for further use. Note that this is only interesting if the value of the parameter is a type itself. Once a type variable V is defined, it can be used to form a pattern (called a <type with var> in the claire syntax in Appendix A) as follows:

<type with var> º <type> | <var> | {<var>} |

tuple(<type with var>seq+ ) |

<class>[á <var>:<type with var> | <var>=<var> ñseq+]

(2) The second advanced typing feature of claire is designed to capture the fine relationship between the type of the output result and the types of the input arguments. When such a relationship can be described with a claire expression e(x1,...,xn), where x1, ..., xn are the types of the input parameters, claire allows to substitute type[e] to the range declaration. It means that the result of the evaluation of the method should belong to e(t1,...,tn) for any types t1,...,tn that contain the input parameters.

For instance, the identity function is known to return a result of the same type as its input argument (by definition !). Therefore, it can be described in claire as follows.

id(x:any) : type[x] -> x

In the expression that we introduce with the type[e] construct, we can use the types of the input variables directly through the variables themselves. For instance, in the "type[x]" definition of the id example, the "x" refers to the type of the input variable. Notice that the types of the input variables are not uniquely defined. Therefore, the user must ensure that her "prediction" e of the output type is valid for any valid types t1, ..., tn of the input arguments.

The expression e may use the extra type variables that were introduced earlier. For instance, we could define the "top" method for stacks as follows.

top(s:stack<X>) : type[X] -> s.content[s.index]

The "second-order type" e (second-order means that we type the method, which is a function on objects, with another function on types) is built using the basic claire operators on types such as U, ^ and some useful operations such as "member". If c is a type, member(c) is the minimal type that contains all possible members of c. For instance, member({c}) = c by definition. This is useful to describe the range of the enumeration method set!. This method returns a set, whose members belong to the input class c by definition. Thus, we know that they must belong to the type member(X) for any type X to who c belongs (cf. definition of member). This translates into the following claire definition.

set!(c:class) : type[set[member(c)]] -> c.instances

For instance, if c belongs to subtype[B] then set!(c) belongs to set[B].

To summarize, here is a more precise description of the syntax for defining a method:

<function> (<vi>:<ti>, i Î (1 .. n)) : <range> -> <exp>

Each type ti for the variable vi is an "extended type" that may use type variables introduced by the previous extended types t1, t2 ... ti-1 . An extended type is defined as follows.

<et> :: <class> | <set> | <var> | (<et> ^ | U <et>) | (<obj> .. <obj>)|

set[<et>] | list[<et>] | <et>[] | tuple(<et>seq) |

<class>[< <var>:<et> | <var>= <<var>|<const>> >seq+]

The <range> expression is either a regular type or a "second order type", which is a claire expression e introduced with the type[e] syntactical construct.

<range> :: <type> | type[<expression>]


4.5 Escaping Types

There are two ways to escape type checking in claire. The first one is casting, which means giving an explicit type to an expression. The syntax is quite explicit:

<cast> :: (<expression> as <type>)

This will tell the compiler that <expression> should be considered as having type <type>. Casting is ignored by the interpreter and should only be used as a compiler optimization. There is, however, one convenient exception to this rule, which is the casting into a list parametric type. When an untyped list is casted into a typed list, the value of its of parameter is actually modified by the interpreter, once the correct typing of all members has been verified. For instance, the two following expressions are equivalent:

list<integer>(1,2,3,4)

list(1,2,3,4) as list<integer>

The second type escaping mechanism is the non-polymorphic method call, where we tell what method we want to use by forcing the type of the first argument. This is equivalent to the super message passing facilities of many object-oriented languages.

<super> º <selector>@<type>(<exp>seq)

The instruction f@c(...) will force claire to use the method that it would use for f(...) if the first argument was of type c (claire only checks that this first argument actually belongs to c).

A language is type-safe if the compiler can use type inference to check all type constraints (ranges) at compile-time and ensure that there will be no type checking errors at run-time. CLAIRE is not type-safe because it admits expressions for which type inference is not possible such as read(p) + read(p). On the other hand, most expressions in CLAIRE may be statically type-checked and the claire compiler uses this property to generate code that is very similar to what would be produced with a C++ compiler. A major difference between CLAIRE 3.0 and earlier versions is the fact that lists may be explicitly typed, which removes the problems that could happen earlier with dynamic types. Lists and sets subtypes support inclusion polymorphism, which means that if A is a subtype of B, list[A] is a subtype of list[B]; for instance list[(0 .. 1)] <= list[integer]. Thus only read operations can be statically type-checked w.r.t. such type information. On the other hand, array subtypes, as well as list or set parametric subtypes, are monomorphic, since A[] is not the set of arrays which contain members of A, but the set of arrays whose member type (the of slot) contains the value A. Thus if A is different from B, A[] is not comparable with B[], and list<A> is not comparable with list<B>. This enables the static type-checking of read and write operations on lists. The fact that claire supports all styles of type disciplines is granted by the combination of a rich dynamic type system coupled with a powerful type inference mechanism within the compiler, and is a key feature of claire.


4.6 Selectors, Properties and Operations

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:.

f :: operation()

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

f(x:integer, y:integer) : ...

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.

abstract(f)

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

inverse(husband) := wife

This can be done for any relation: slots and tables (cf. Section 5). 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[1] and the set associated with an object x is supposed to be the set of values associated with x through the relation. Notice that other aspects of multi-valuation were covered in Section 2.2.

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:

s(x) = y <=> r(y) = x

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:

s(x) = y <=> x € r(y)

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.



[1] This slot can be reset to false in the rare case when the relation should actually be seen as mono-valued.


4.7 Iterations

We just saw that claire will produce in-line substitution for some methods. This is especially powerful when combined with parametric function calls (using call(...)) taking advantage of the fact that claire is a functional language. There is another form of code substitution supported by claire that is also extremely useful, namely the iteration of set data structure.

Any object s that understands the set! method can be iterated over. That means that the construction for x in s e(x) can be used. The actual iteration over the set represented by s is done by constructing explicitly the set extension. However, there often exists a way to iterate the set structure without constructing the set extension. The simplest example is the integer interval structure that is iterated with a while loop and a counter.

It is possible to define iteration in claire through code substitution. This is done by defining a new inline restriction of the property iterate, with signature (x:X,v:Variable,e:any). The principle is that claire will replace any occurrence of (for v in x e) by the body of the inline method as soon as the type of the expression x matches with X (v is assumed to be a free variable in the expression e). For instance, here is the definition of iterate over integer intervals:

iterate(x:interval[min:integer,max:integer],v:Variable,e:any)

=> let v := min(x), %max := max(x) in (while (v <= %max) (e, v :+ 1))

Here is a more interesting example. We can define hash tables as follows. A table is defined with a list (of size 2n – 3 where n is the index), which is full of “unknown” except for these objects that belong to the set. Each object is inserted at the next available place in the table, starting at a point given by the hashing function (a generic hashing function provided by claire: hash).

htable <: object( count:integer = 0,

index:integer = 4,

arg:list<any> = list<any>())

set!(x:htable) -> {y in x.arg | known?(y)}

insert(x:htable,y:any)

-> let l := x.arg in

(if (x.count >= length(l) / 2)

(x.arg := make_list(^2(x.index - 3), unknown),

x.index :+ 1, x.count := 0,

for z in {y in l | known?(y)} insert(x,z),

insert(x,y))

else let i := hash(l,y) in

(until (l[i] = unknown | l[i] = y)

(if (i = length(l)) i := 1 else i :+ 1),

if (l[i] = unknown)

(x.count :+ 1, l[i] := y)))

Note that claire provides a few other functions for hashing that would allow an even simpler, though less self-contained, solution. To iterate over such hash tables without computing set!(x) we define

iterate(s:htable, v:Variable, e:any)

=> (for v in s.arg (if known?(v) e))

Thus, claire will replace

let s:htable := ... in sum(s)

by

let s:htable := ... in

(let x := 0 in

(for v in s.arg

(if known?(v) x :+ v),

x))

The use of iterate will only be taken into account in the compiled code unless one uses oload, which calls the optimizer for each new method. iterate is a convenient way to extend the set of CLAIRE data structure that represent sets with the optimal efficiency. Notice that, for a compiled program, we could have defined set! as follows (this definition would be valid for any new type of set).

set!(s:htable) -> {x | x in s}

When defining a restriction of iterate, one must not forget the handling of values returned by a break statement. In most cases, the code produce by iterate is itself a loop (a for or a while), thus this handling is implicit. However, there may be multiples loops, or the final value may be distinct from the loop itself, in which case an explicit handling is necessary. Here is an example taken from class iteration:

iterate(x:class,v:Variable,e:any) : any

=> (for %v_1 in x.descendents

let %v_2 := (for v in %v_1.instances e) in // catch inner break

(if %v_2 break(%v_2))) // transmit the value

Notice that it is always possible to introduce a loop to handle breaks if none are present; we may replace the expression e by:

while true (e, break(nil))

Last, we need to address the issue of parametric polymorphism, or how to define new kinds of type sets. The previous example of hash-sets is incomplete, because it only describes generic hash-sets that may contain any element. If we want to introduce typed hash-sets, we need to follow these three steps. First we add a type parameter to the htable class:

htable[of] <: object( of:type = any, count:integer = 0, ...)


Second, we use a parametric signature to use the type parameter appropriately :

insert(x:htable<X>,y:X) -> ...

Last, we need to tell the compiler that an instance from htable[X] only contains objects from X. This is accomplished by extending the member function which is used by the compiler to find a valid type for all members of a given set. If x is a type, member(x) is a valid type for any y that will belong to a set s of type x. If T is a new type of sets, we may introduce a method member(x :T, t :type) that tells how to compute member(t) if t is included in T. For instance, here is a valid definition for our htable example:

member(x:htable,t:type) -> member(t @ of)

This last part may be difficult to grasp (do not worry, this is an advanced feature). First, recall that if t is a type and p a property, (t @ p) is a valid type for x.p when x is of type t. Suppose that we now have an expression e, with type t1, that represents a htable (thus t1 <= htable). When the compiler calls member(t1), the previous method is invoked (x is bound to a system-dependent value that should not be used and t is bound to t1). The first step is to compute (t1 @ of), which is a type that contains all possible values for y.of, where y is a possible result of evaluating e. Thus, member(t1 @ of) is a type that contains all possible values of y, since they must belong to y.of by construction. This type is, therefore, used by the compiler as the type of the element variable v inside the loop generated by iterate.

Iteration is equally covered in the section 3.6 of the Appendix C, with the ability to optimize the iteration of specific language expressions. This kind of tuning is outside the scope of regular claire usage, but is provided to make claire a great tool to build DSL (Domain Specific Languages).