Closures
Introduction
Closures are a handy extension to the base language. Not only they make code more consise, but you can use them as a vehicle to carry you through the lands of functional paradigm in programming. You can treat functions as first-class citizens in your programs - store them in variables, pass them around to methods as arguments or have methods and functions return other functions. The MPS Closures Support allows to you employ closures in your own languages. In fact, MPS itself uses closures heavily, for example, in the collections language.
This language loosely follows the "BGGA" proposal specification for closures in Java and is more powerful than lambdas in Java. The MPS closures are generated into Java lambdas, provided they avoid these four incompatibilities:
Yield operations
“Functional” abstract classes
Annotations
Local variables conflicting with parent scope
Otherwise, the closures are generated into anonymous inner classes. Only the closures runtime jar file is required to be on the classpath of the generated solutions.
Function type
{ Type1, Type2... => ReturnType }
Let's start with a trivial example of function type declaration. It declares a function that accepts no parameters and returns no value.
Subtyping rules
A function type is covariant by its return type and contravariant by parameter types.
For example, given we have defined a method that accepts {String => Number} :
we can pass an instance of {Object => Integer} (a function that accepts Object and returns int) to this method:
Simply put, you can use different actual types of parameters and the return value so long as you keep the promise made in the super-type's signature.
Closure literal
Closure literal is created simply by entering a following construct: { <parameter decls> => <body> }. No "new" operator is necessary.
The result type is calculated following one or more of these rules:
last statement, if it's an ExpressionStatement;
return statement with an expression;
yield statement.
Note: it's impossible to combine return and yield within a single closure literal.
Closure invocation
The invoke operation is the only method you can call on a closure. Instead of entering
To invoke a closure, it is recommended to use the simplified version of this operation - parentheses enclosing the parameter list.
Invoking a closure then looks like a regular method call.
Some examples of closure literal definitions.
Recursion
Functional programing without recursion is like making coffee without water, so you have a natural way to recursively call a closure from within its body:
A standalone invoke within the closure's body calls the current closure.
Closure conversion
For practical purposes a closure literal can be used in places where an instance of a single-method interface is expected, and vice versa.
The generated code is exactly the same as when using anonymous class:
Think of all the places where Java requires instances of Runnable, Callable or various observer or listener classes:
As with interfaces, an abstract class containing exactly one abstract method can also be adapted to from a closure literal. This can help, for example, in smooth transition to a new API, when existing interfaces serving as functions can be changed to abstract classes implementing the new interfaces.
Yield statement
The yield statement allows closures populate collections. If a yield statement is encountered within the body of a closure literal, the following are the consequences:
if the type of yield statement expression is Type, then the result type of the closure literal is sequence<Type>;
all control statements within the body are converted into a switch statement within an infinite do-while loop at the generation;
usage of return statement is forbidden and the value of last ExpressionStatement is ignored.
Functions that return functions
A little bit of functional programming for the functional hearts out there:
The curry() method is defined as follows:
Runtime
In order to run the code generated by the closures language, it's necessary to add to the classpath of the solution the closures runtime library. This jar file contains the synthetic interfaces needed to support variables of function type and some utility classes. It's located in: %MPS_HOME%/core/baseLanguage/jetbrains.mps.baseLanguage.closures.runtime.jar
Differences from the BGGA proposal
No messing up with control flow. This means no support for control flow statements that break the boundaries of a closure literal.
No "early return" problem: since MPS allows return to be used anywhere within the body.
The yield statement.
[1] Closures for the Java Programming Language
[2] Version 0.5 of the BGGA closures specification is partially supported
[3] This is no longer true: only closure literal to interface conversion is supported, as an optimization measure.