Contents

Introduction

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.

Parameters

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.

Library procedures

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.

Contents