2.19.  Lambda Expressions and functions

Kermeta proposes an implementation of lambda expressions, that is useful for example when implementing OCL-like functions.

2.19.1.  Syntax

  • 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 */}
    [Note] New in Kermeta 2

    It is now possible to ommit the variable when calling an operation that uses a single lambda parameter with Void input type.

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

2.19.2. Some existing useful 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)}

2.19.3. Defining new functions in a class

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
}

2.19.4. Defining lambda expression variables

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))
    }

2.19.5. Useful patterns that use lambda

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