One feature which all new programming languages seem to have, but which is missing from Icon (and Object Icon), is the anonymous nested function. However, it is quite easy to simulate something very similar, using co-expressions and objects.
The goal is to have a procedure (call it lambda
) which takes a co-expression, and returns another callable procedure, with the co-expression as its body. For example :-
f := lambda{ 1 to 10 }
This would assign to f a procedure (or something that behaves like a procedure), which when called each time would generate 1 to 10.
Obviously there is also the question of parameters (and local variables), but for the moment just consider the case with no parameters or locals. This can easily be achieved by using a thing I term a “method pointer” (or “methp” for short), as follows :-
class Lambda()
private e
public call()
suspend !e
end
public new(e)
self.e := e
return
end
end
procedure lambda(e)
return Lambda(e).call
end
The idea here is that lambda
first creates an instance of Lambda
, which stores a reference to e
, the co-expression which is the body of the anonymous function. Then, lambda
returns a method pointer to the call
method of that instance. When this method pointer is later invoked, the call()
method on the instance is invoked, and this generates the results of e
.
Turning now to the question of parameters, note that the above call
method doesn’t take any parameters. If it did, how could they be communicated to the co-expression e
?
One way would be to send the parameters as the value transmitted during co-expression activation. call()
could be rewritten as follows :-
public call(a[])
local t
t := ^e
@t
suspend a@t | |@t
end
This could be used in the following way :-
local f, p
...
f := lambda {{
# Get the parameters
p := coact()
# Produce the results
(p[1] + 1) | (p[2] - 1)
}}
Calling f(100, 200)
would then generate 101
and 199
.
This has a couple of problems. Firstly, it is clumsy, which rather defeats the point of an anonymous function. Secondly, it requires the use of a local variable in the enclosing procedure to store the parameters. This may be a problem if the function is used twice concurrently, since the two calls may interfere with one another. To see this, consider the expression
f(100, 200) + f(1000, 2000)
This should generate the four values 1102, 2100, 1200 and 2198. Instead it generates 1102, 2100, 3000 and 3998, because the second invocation overwrites the value of p
used by the first.
This problem obviously isn’t insurmountable, but a solution would make the code even clumsier.
Another approach is to use a global variable to pass the parameters. So we would change call
to :-
global _a
...
public call(a[])
_a := a
suspend !e
end
And use it as follows :-
f := lambda{ _a[1] + 1 | _a[2] - 1 }
Of course, this still suffers from the problem that one call may interfere with another’s use of _a
. However, this is quite easily fixed, because the call
method has access to _a
, and can thus save and restore its value as necessary. Saving and restoring global state is a general problem, and there is a library procedure, util.save_state
, to help. See this page for more details.
The above techniques are used in the library procedure ipl.functional.lambda
. In addition to a global variable _a
for the arguments, _l
is provided for notional local variables to be used by the anonymous function. For example :-
f := lambda{ (_l := 0, _l +:= 1 to _a[1], ._l) }
Given this, f(5)
would generate 1, 3, 6, 10, 15. Note how _l
is dereferenced, to avoid returning a variable reference to _l
. If this wasn’t done, the caller would dereference it, and get the value restored by save_state
, probably &null
.
An instance of util.State
is provided, ipl.functional.LambdaState
, which saves and restores _a
and _l
, in effect giving each lambda function it its own copy of these variables. The call
method uses save_state
, as follows :-
public call(a[])
suspend save_state(^e, LambdaState(a))
end
There is another procedure, ipl.functional.lambda1
, which behaves just like lambda
, except that it sets _a
to the first argument, rather than the argument list. This makes functions which use just one argument cleaner; for example the one above could be rewritten :-
f := lambda1{ (_l := 0, _l +:= 1 to _a, ._l) }
As a final example, here is a recursive function to sum a flattened list structure; for example f([1,[2,4],[5,[6,7]]]) gives 25.
f := lambda1{{
if type(_a) == "list" then {
_l := 0
every _l +:= f(!_a)
} else
_l := _a
._l
}}
The _l
variable is invaluable here since each invocation needs its own copy of that variable; if they shared a local variable in the enclosing procedure the results would certainly be wrong! Note again how _l
must be dereferenced when it provides the value of the function.
One cautionary note about the above function is that its recursion depth isn’t limited by &maxlevel
in the ordinary way, for the following reason. &maxlevel
limits the call depth in each individual co-expression. However, our above function creates one new co-expression on each invocation, to execute the body of the function. Within each of those co-expression, the call depth never exceeds three. So, if we were to introduce a bug causing runaway recursion into the above function the result would unfortunately be to allocate more and more co-expressions until memory was exhausted.
The above is rather involved, inefficient and clumsy. If you don’t mind using an auxiliary helper procedure or method, here are some better alternatives.
Within the context of a class, if you just wish to reference the enclosing object instance, but don’t wish to reference any local variables, consider using a simple method pointer. In other words, instead of :-
public meth1()
local f
...
f := lambda1{ ... }
...
end
use
public meth1()
local f
...
f := meth2
...
end
private meth2(a)
....
end
However, if you do wish to reference local variables in the function, consider passing them using ipl.functional.lpartial
. For example :-
public meth1()
local f, v1, v2
...
f := lpartial(meth2, v1, v2)
...
end
private meth2(v1, v2, a)
....
end
Within the context of a procedure (rather than a method), consider using a package-level helper procedure. This will be invisible outside the package :-
procedure proc1()
local f, v1, v2
...
f := lpartial(helper, v1, v2)
...
end
package procedure helper(v1, v2, a)
....
end
When using lpartial
, the local variables are of course copied, so the auxiliary function cannot modify the originals. If you wish to do that, then it is best to create an auxiliary object (or record) to store them, and pass a reference to that instead. In some circumstances, it may even be preferable to move one or both methods into this auxiliary class.