Kermeta proposes an implementation of lambda expressions, that is useful for example when implementing OCL-like functions.
Definition of the type of the function:
Kermeta has a special type called "function type" that can be used in several places.
This type can be used in all the location a type is expected, in operation parameter, return type of an operation, variables, attributes, references. The most typical use is in the parameter of an operation.
// function type that takes one parameter as input <TYPE->RETURN_TYPE> // function type that takes several input parameters [TYPE_1, TYPE_2, ...]->RETURN_TYPE> // replace here the ... with as many type you wish // function type that takes one input parameter of type Void can be called without parameter <Void->RETURN_TYPE>
When an operation has only one parameter of type "function", then a special shortcut syntax allows to ommit the perenthesis when calling this operation.
Examples of definition and call of operations that use only one function as parameter:
Operation with a function parameter that uses one input:
operation operationWithOneLambdaParameter ( lambda : <INPUT_TYPE->RETURN_TYPE> ) : Void is do var foo : INPUT_TYPE init INPUT_TYPE.new lambda(foo) // will call the lambda with foo) end [...] // example of call // calling with the full syntax operationWithOneLambdaParameter({ i : INPUT_TYPE | /* do something on i and return something of kind RETURN_TYPE */}) // calling with the shortcut syntax : the type of i and the parenthesis can be ommitted operationWithOneLambdaParameter{ i | /* do something on i and return something of kind RETURN_TYPE */}
Operation with a function parameter that uses several inputs:
// function with several parameters, (the example use 2 parameters but you can use more than 2 ...) operation operationWithSeveralLambdaParameters ( mylambda : <INPUT_TYPE1, INPUT_TYPE2->RETURN_TYPE> ) : Void is do var foo : INPUT_TYPE1 init INPUT_TYPE1.new var bar : INPUT_TYPE2 init INPUT_TYPE2.new mylambda(foo, bar) // will call the lambda end [...] // example of call // calling with the full syntax operationWithSeveralLambdaParameters({ x, y : INPUT_TYPE | /* do something on x and y and return something of kind RETURN_TYPE */}) // calling with the shortcut syntax : the type of i and the parenthesis can be ommitted operationWithSeveralLambdaParameters{ x, y | /* do something on x and y and return something of kind RETURN_TYPE */}
New in Kermeta 2 | |
---|---|
It is now possible to ommit the variable when calling an
operation that uses a single lambda
parameter with
|
Operation with a function parameter that uses a single input of
type
Void
:
// With 0 parameter operation operationWithSeveralLambdaParameters ( lambda : <Void->RETURN_TYPE> ) : Void is do lambda(void) // will call the lambda lambda() // void can be ommitted to call the lambda, however the parenthesis are still mandatory end [...] // example of call // calling with the full syntax operationWithSeveralLambdaParameters({ void : Void | /* do something and return something of kind RETURN_TYPE */}) // calling with the shortcut syntax : the type, the variable and the parenthesis can be ommitted operationWithSeveralLambdaParameters{ | /* do something and return something of kind RETURN_TYPE */}
The operation
indexedEach
and
forAllCpl
on
Collection
are examples of such lambda with several input parameters. (See
section bellow).
Using the full syntax you can create operations that use parameters of mixed type including function type:
operation operationWithMixedParameters ( f : <INPUT_TYPE->RETURN_TYPE>, otherParam : String ) : Void is do var foo : INPUT_TYPE init INPUT_TYPE.new f(foo) // will call the lambda // do something with otherParam end [...] // example of call // only the full syntax is allowed operationWithSeveralLambdaParameters({ x : INPUT_TYPE | /* do something on x and return something of kind RETURN_TYPE */}, "hellow world")
Declaring and defining an "anonymous" function inside a variable for reuse.
var f1 : <TYPE -> RETURN_TYPE> f1 := { var_name : TYPE | /* SOME_CODE_WITH_RETURN_TYPE_RESULT */ } var f2 : <[ TYPE_1, TYPE_2, TYPE_3 ] -> RETURN_TYPE> f2 := { var_name_1 : TYPE_1, var_name_2 : TYPE_2, var_name_3 : TYPE_3 | /* SOME_CODE_WITH_RETURN_TYPE_RESULT */ }
Tip | |
---|---|
You can also use function type in reference or attribute to store them. However, since ecore doesn't know about them, you'll have to define them on the kermeta side and thus cannot be serialized in a model. |
In the following sections, you will find many examples of declarations, definitions, and uses of functions.
The collections in Kermeta implement several functions based on lambda expression. These ones are very useful for model navigation.
Example 1: closure definition for collection iterator-like in Kermeta
aCollection.each { e | /* do something with each element e of this collection */ }
See Section 2.15, “ Collections ” for other existing functions on collections.
Example 2: another useful function that is defined on Integer : the function times
10.times { i | stdio.writeln(i.toString) } // prints 0 to 9
Notice that you can also write some complex code in the function, using internal variables, etc. In such a case, the last statement evaluation will be the returned result of the lambda expression (provided it is declared to return something)
Example 3 : "complex"" code in a lambda expression
aCollection.each { e | stdio.writeln("I am complex code!" stdio.writeln("Element : " + e.toString) var i : Integer init 5 i := i + 132458 }
Example 4 : postponing parameter evaluation in the andThen operation
The operation andThen and orElse of Boolean use the ability of functions to postpone the parameter evaluation. This allows to implement the expected behavior without adding new construct to the language.
// this kind of code allows to avoid a cast exception on the second test if cl.isInstanceOf(NamedElement).andThen{| cl.asType(NamedElement).name == "foo"} then // do something... end
Example 5 : using operation that use a function with several inputs
aCollection.indexedEach { e, eachContext | stdio.write("element "+ eachContext.index.toString + ": " + e.toString) if(!eachContext.isLast) then stdio.writeln(",") end }
aCollectionOfNamedElement.forAllCpl{s1,s2| (s1.name==s2.name).implies(s1==s2)}
You can also define your own functions, by declaring an operation, with a parameter as a function using the syntax described in Section 2.19.1, “ Syntax ” .
Example : definition of functions for collections
abstract class Collection<G> { /** * runs func on each element of the collection */ operation each(func : <G -> Object>) : Void is do from var it : Iterator<G> init iterator until it.isOff loop func(it.next) end end
/** * checks that the condition is true on all the element of the collection * returns true if the collection is empty */ operation forAll(func : <G -> Boolean>) : Boolean is do var test : Boolean init true from var it : Iterator<G> init iterator until it.isOff loop test := test and func(it.next) end result := test end }
You can also define lambda expression as variable. This can be useful if you don't want to ( or can't) modify the class.
A basic lambda expression
With one Integer argument and returning an Integer.
var aLambdaExp : <Integer->Integer> var aLambdaResult : Integer aLambdaExp := { i : Integer | i.plus(4) } // aLambdaResult equals 7 aLambdaResult := aLambdaExp(3)
A lambda expression with several parameters
var aLambdaExp : <[Integer, Integer]->Integer> var aLambdaResult : Integer aLambdaExp := { i : Integer, j : Integer | i * j } // aLambdaResult equals 12 aLambdaResult := aLambdaExp(3, 4)
A lambda expression on a collection
var init_set : Set<Integer> := Set<Integer>.new init_set.add(32) init_set.add(23) init_set.add(41) // This sequence equals : [320, 230, 410] var sequence : Sequence<Integer> := init_set.collect { element | element*10}
The code within the function can be as complex as you want, using internal variables, etc.
var factoExp : <Integer->Integer> factoExp := { n : Integer | var fact : Integer := 1 from var x : Integer := 1 until x > n loop fact := fact * x x:=x+1 end fact // return fact as the result of the function //shorter alternative ;-)... if n<=1 then 1 else factoExp(n -1) * n end } var cnkExp : <[Integer, Integer]->Integer> cnkExp := { n : Integer, k : Integer | factoExp(n) / (factoExp(k) * factoExp(n-k)) }
When starting to use the lambda expression you can define some
useful design patterns. One of
them is the ability to propose an
each
that traverses all the owned element of a given metamodel. For some
usage, this can be an
interresting alternative to the visitor
design pattern.
Let's have the following metamodel:
class NamedElement { attribute name : String } class A inherits NamedElement { attribute ownedAs : A[0..*] } class B inherits A { attribute ownedBs : B[0..*] } class C inherits A { attribute ownedBs : B[0..*] attribute ownedC : C }
It would be nice to be able to call a simple lambda on all the elements directly owned by an element. This can be done by adding an operation using the following pattern:
aspect class NamedElement { operation eachOwnedElement(func : <NamedElement -> Void>) : Void is do // String attributes are value, so they aren't really "owned" end } aspect class A inherits NamedElement { operation eachOwnedElement(func : <NamedElement -> Void>) : Void is do super[NamedElement](func) self.ownedAs.each{ e | func(e) } end } aspect class B inherits A { operation eachOwnedElement(func : <NamedElement -> Void>) : Void is do super[A](func) // makes sure to call func on attributes inherited from A self.ownedBs.each{ e | func(e) } end } aspect class C inherits A { operation eachOwnedElement(func : <NamedElement -> Void>) : Void is do super[A](func) // makes sure to call func on attributes inherited from A self.ownedBs.each{ e | func(e) } if(not ownedC.isVoid) then func(ownedC) end end }
Then a call like the following will ensure to traverse all the directly owned elements.
var c : C := // initialize it with a complex model :-) // write the name of all the elements directly contained by c c.eachOwnedElement{ aNamedElement | stdio.writeln(aNamedElement.name) } // collect all the elements directly contained by c var ownedElements : Sequence<Object> := Sequence<Object>.new c.eachOwnedElement{ aNamedElement | ownedElements.add(aNamedElement) } stdio.writeln("ownedElements.size = "+ ownedElements.size.toString)
It is then easy to extend this pattern to traverse the directly and indirectly owned elements.
aspect class NamedElement { operation eachAllOwnedElement(func : <NamedElement -> Void>) : Void is do eachOwnedElement{e| func(e) e.eachAllOwnedElement{child|func(child)} } end }
This new operation can be called in the same easy way:
// write the name of all the elements directly or indirectly contained by c c.eachAllOwnedElement{ aNamedElement | stdio.writeln(aNamedElement.name) } // collect all the elements directly or indirectly contained by c var allownedElements : Sequence<Object> := Sequence<Object>.new c.eachAllOwnedElement{ aNamedElement | allownedElements.add(aNamedElement) }
Tip | |
---|---|
Like any pattern, this task can be automated by using a model transformation. The Ecore MDK offers such transformation that generates this pattern for a given Ecore model. |