Routine and iter closures are similar to the 'function pointer' and 'closure' constructs of other languages. They bind a reference to a method together with zero or more argument values (possibly including self). The type of a closure begins with the keywords ROUT or ITER and followed by the modes and types of the underscore arguments, if any, enclosed in braces (e.g. 'ROUT{A, out B, inout C}', 'ITER{once A, out B, C}'). These are followed by a colon and the return type, if there is one (e.g. 'ROUT{INT}:INT', 'ITER{once INT}:FLT').
A closure is created by an expression that binds a routine or an iterator, along with some of its arguments. The outer part of the expression is 'bind(...)'. This surrounds a routine or iterator call in which any of the arguments or self may have been replaced by the underscore character '_'. Such unspecified arguments are unbound. Unbound arguments are specified when the closure is eventually called.
a:ROUT{INT}:INT := bind(3.plus(_)) b:ITER:INT := bind(3.times!); |
Out and inout arguments must be specified in the closure type. If the routine has inout or out arguments
swap(inout x, inout y:INT) is tmp ::= x; x := y; y := tmp; end; |
as show below, they are mentioned in the type of the closure:
The routine 'swap' swaps the values of the two arguments, 'x' and 'y'. 'r' is a closure for binding the 'swap' routine.
r:ROUT{inout INT, inout INT} := bind(swap(_,_)); |
Each routine closure defines a routine named 'call' and each iterator closure defines an iterator named 'call!'. These have argument and return types that correspond to the closure type specifiers. Invocations of these features behave like a call on the original routine or iterator with the arguments specified by a combination of the bound values and those provided to call or call!. The arguments to call and call! match the underscores positionally from left to right .
The previously defined closures are invoked as shown
#OUT + a.call(4); -- Prints out 7, where a is bind(3.plus(_) sum:INT := 0; loop sum := sum + b.call!; end; #OUT + sum; -- Prints out 3 (0+1+2) |
In the following example, we define a bound routine that takes an INT as an argument and returns an INT.
br:ROUT{INT}:INT := bind(1.plus(_)); #OUT + br.call(9); -- Prints out '10' |
The variable br is typed as a bound routine which takes an integer as argument and returns an integer. The routine 1.plus, which is of the appropriate type, is then assigned to br. The routine associated with br may then be invoked by the built in function call. Just as we would when calling the routine INT::plus(INT), we must supply the integer argument to the bound routine.
When binding a routine which is overloaded, there might be some ambiguity about which routine is meant to be bound
class FLT is plus(f:FLT):FLT -- add self and 'i' and return the result plus(i:INT):FLT; -- add self and 'f' (after converting 'i' to FLT) end; |
When binding the plus routine, it might not be obvious which routine is intended
b ::= bind(_.plus(_)); |
In case of ambiguity, the right method must be determined by the context in which the binding takes place.
If there is ambiguity about which method is to be bound, the type of the variable must be explicitly specified
b:ROUT{FLT,FLT}:FLT := bind(_.plus(_)); -- Selects the first 'plus' |
A method may also be bound at the time a call is made. The type of the closure is determined by the type of the argument in the call.
reduce(a:ARRAY{FLT}, br:ROUT{FLT,FLT}:FLT):FLT is res:FLT := 0.0; loop el:FLT := a.elt!; res := br.call(res,el); end; return res; end; |
We can call the reduction function as follows:
a:ARRAY{FLT} := |1.0,7.0,3.0|; #OUT + reduce(a,bind(_.plus(_))); -- Prints '11.0', the sum of the elements of 'a' |
The second argument to the function reduce expects a ROUT{FLT,FLT}:FLT and this type was used to select which plus routine should be bound. When there could be doubt about which routine is actually being bound, it is very good practice to specify the type explicitly
r:ROUT{FLT,FLT}:FLT := bind(_.plus(_)); #OUT + reduce(a,r); |
out and inout arguments must be left unbound. This is a reasonable restriction, since such arguments must return a value to the calling context. If such an argument were bound, when the closure is invoked, variables that existed at the point of closure binding would be affected. Such variables might not even be alive at the point where the closure is actually invoked.
When a routine closure is created, it can preset some of the values of the arguments.
class MAIN is foo(a:INT, b:INT):INT is return(a+b+10); end; main is br1:ROUT{INT,INT}:INT := bind(foo(_,_)); br2:ROUT{INT}:INT := bind(foo(10,_)); #OUT + br1.call(4,3) + "," + br2.call(9); -- Should print 17 and 29 end; end; |
In the example above, br2 binds the first argument of foo to 10 and the second argument is left unbound. This second argument will have to be supplied by the caller of the bound routine. br1 binds neither argument and hence when it is called, it must supply both arguments.
Here we double every element of an array by applying a routine closure r to each element of an array.
r :ROUT{INT}:INT := bind(2.times(_)); loop a.set!(r.call(a.elt!)) end |
bound routines are often used to apply a function to arbitrary objects of a particular class. For this usage, we need the self argument to be unbound. This illustrates how self may be left unbound. The type of self must be inferred from the type context (ROUT{INT}).
r:ROUT{INT} := bind(_.plus(3)); #OUT + r.call(5); -- prints '8' |
In the following example we will make use of the plus routine from the INT class.
... from the INT class plus(arg:INT):INT is ... definition of plus main is plusbr1:ROUT{INT,INT}:INT := bind(_.plus(_)); -- self and arg unbound br1res:INT := plusbr1.call(9,10); -- Returns 19 plusbr2:ROUT{INT}:INT := bind(3.plus(_)); -- Binding self only br2res:INT := plusbr2.call(15); -- Returns 18 plusbr3:ROUT{INT}:INT := bind(_.plus(9)); -- Binding arg only br3res:INT := plusbr3.call(11); -- Returns 20 #OUT + br1res + "," + br2res + "," + br3res; -- 19,18,20 end; |
In the above example, plusbr1 leaves both self and the argument to plus unbound. Note that we must specify the type of self when creating the bound routine, otherwise the compiler cannot know which class the routine belongs to (the type could also be an abstract type that defines that feature in its interface). plusbr2 binds self to 3, so that the only argument that need be supplied at call time is the argument to the plus. plusbr3 binds the argument of plus to 15, so that the only argument that need be supplied at call time is self for the routine.