In particular, a function or procedure may be defined inside another function or procedure, and it may still use any values or variables defined in that function or procedure, even when it is returned by it.
Note that when the returned function or procedure is called later, the values of variables may have changed since the time that it was passed.
Here is an example, beware this example contains quite a lot of subtle features!
1: include "std"; 2: 3: var j = 2; 4: 5: fun get_printer(i:int): unit->void = { 6: proc print() { print (i+j); } 7: return print of (unit); 8: } 9: 10: val print_i = get_printer(40); 11: print_i(); endl; 12: j = 3; 13: print_i(); endl; 14:
unit->voidThis is the notation for a procedure type accepting a unit argument.
Notice also that the return value is
print of (unit)This is the name of the function called 'print' which accepts a unit argument. Felix can perform overload resolution when a function name is applied to an argument, by inspecting the type of the argument. In other circumstances, you have to specify the type of the argument.
There is a subtle point of the overloading system here. The procedure 'print of (unit)' actually calls the procedure 'print of (int)'. Now if you are used to C++, you may wonder how this would work: the search for 'print of (int)' stats by finding 'print of (unit)', and in C++ it would stop right there and report an error. In Felix, if overload resolution for an unqualified function or procedure name fails, the enclosing scope is examined. This 'overloading across scopes' is allows you to extend an overload set locally. The special rule does not apply to qualified names (where the name must be found in the designated scope). [I may change this!]
Another thing to notice is that the call to print_i cannot be abbreviated by leaving the () off. This is because print_i is a function value, not the name of a function.
More precisely, the function specification for p of (unit) is used to build a function closure: this consists of the code body of the function, together with the environment at the point of definition and the time the closure is created: it binds to the current 'activation record' or 'stack frame' of the enclosing function 'get_printer'. And because that environment contains the value 'i' initialised to 30, and the variable 'j', it will print 40 + j as an answer when called. This is 42 the first time, since at that time j is 2, and 43 the second time, since j is then 3.
The difference between a function specification and a function closure may seem difficult to grasp at first, but the implementation makes the distinction quite clear: a function specification is a C++ class, whereas a function closure is an object of that class. Function closures exist at run time, function specifications only exist at compile time. Unfortunately perhaps, the unqualified term "function" is used to refer to both. Even more confusing, it sometimes means procedures as well (even though they're utterly different beasts).