Lists, Sets and Instructions
array
categories
Methods and Types
Methods
Types

Methods and Types

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* 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))
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 listargs. 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 :
 [my_max(l:list[integer]) : tuple(integer,integer)
 -> let x1 := 1000000000x2 := 1000000000
     in (for y in l
         (if (y < x1) (x2 := x1x1 := y)
         else if (y < x2x2 := 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:= my_max(list{f(i| i in (1 .. 10)})
 in ...
Each time you use a tuple-assignment for a tuple-method, the compiler uses an optimization technique to use the tuple virtually without any allocation. This makes using tuple-valued methods a safe and elegant programming technique.

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)
It is important to notice that in CLAIRE, methods can have at most 20 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. 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 :+ yx)

 min(s:subtype[integer], f:property: integer
     => let x := 0empty := true
         in (for y in s
             (if empty (x := yempty := false)
             else if call(f,y,xx := 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 :+ yx)

 let x := 0empty := truey := 1max := 10
 in (while (y <= max)
         (if (f(y) > 0)
             (if empty (x := yempty := false)
             else if (y > xx := 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(lmy_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.

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(lmy_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 '[mmm'. 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 :

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 to support dynamic dispatch in a efficient manner, as long as the property is uniform.


categories Methodsnormal dispatch Language method

interface(p:property) -> void

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 Methodsnormal dispatch Language method

interface(c:class U Union, pi:listargs) -> void

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.