| Lists, Sets and Instructions array |
categories Methods and Types Methods |
Types |
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* A2* ... * An, also represented as list(A1, ...,An) or A1* A2* ... * 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* A2* ... * An-1. |
If two methods have intersecting signatures and the property is called on objects in 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.
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)) |
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 listargs. For instance, if we define :
| [f(x:integer,y:listargs) -> x + size(y)] |
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 :
| [my_max(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))] |
| let (a,b) := my_max(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>] |
| f(x:integer,y:integer) -> function!(my_version_of_f) cos(x:float) -> function!(cos_for_claire) |
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. 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) |
| 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) |
| mymin(l:list[integer]) : integer -> min(l, my_order) |
| [mymin(l:list[integer]) : integer -> min(l, my_order)] |
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 :
| abstract(p) |
| final(p) |
| categories | Methods | normal dispatch | Language method |
Activate fast dispatch on the property p, that is dynamic calls should be optimized. p is meant to be a uniform property such the optimization can take place.
| categories | Methods | normal dispatch | Language method |
This new method (in CLAIRE 3.1) is used to associate the interface status to a property or a set of properties. Within a class (through the use of interface(c, p1, ...)), this means that a member method will be generated for the C++ class associated to c. Note that this definition requires the presence of a method pi @ C for each property pi. In CLAIRE 3.1, a union (c1 U c2 ... U c3) can be used instead of a class, which is an elegant way to factor the interface declaration for c1, ... cn.