The future of lambda delegates

kris foo at bar.com
Wed Aug 16 09:53:20 PDT 2006


Mikola Lysenko wrote:
> D's delegates are great.  They are vastly superior to C++'s confused 
> pointer-to-member functions, and much more useful than Java's adapter 
> classes.  In the world of system programs, nothing comes close.  One 
> interesting type of delegate is the anonymous-nested-delegate-literal, or 
> "lambda" delegate.  For those unacquainted with such niceties, here are the 
> documentation links:
> http://www.digitalmars.com/d/function.html#nested
> http://www.digitalmars.com/d/expression.html#FunctionLiteral
> 
> Recent D releases (notably 0.161) have updated the syntax and improved the 
> overall usability of lambda delegates. All the syntactic sugar is nice, but 
> it also lays a few traps for the unwary.  Here is a simple example:
> 
> // The Fibonacci numbers are an integer sequence such that
> //        F(n) = F(n - 1) + F(n - 2)
> //        And F(0) = 0, F(1) = 1
> int delegate() fibs()
> {
>     int a = 0;            // Initialize the last two terms of the Fibonacci 
> sequence
>     int b = 1;
> 
>     return
>     {
>         int c = a + b;     // Calculate the next term in the sequence
>         a = b;               // Shift the previous terms back
>         b = c;
>         return c;            // Return the result
>     };
> }
> 
> This function returns a function which will sequentially evaluate all of the 
> Fibonacci numbers.  Notice that the inner delegate modifies the variables a 
> and b in fibs() scope.  Because of this, it is not guaranteed to work after 
> fibs returns.  This is most irritating, and it greatly restricts the use of 
> this technique. Another potential use for lambda delegates is to create 
> one-line adapter methods.  Consider the following attempt to create a button 
> which will display an arbitrary message when clicked:
> 
> Button createButton(char[] click_msg)
> {
>     Button b = new Button();
>     b.mouseClickCallback = { MsgBox(click_msg); };
>     return b;
> }
> 
> Once more, this sort of method relies on access to the outer function scope 
> from within the lambda delegate.  Given the current semantics, this code is 
> doomed to fail in any number of random ways.  As a final example, suppose we 
> want to perform a lengthy calculation in a separate thread, and only wait 
> for the value once it is needed.  In this case, one could try to do the 
> following:
> 
> int[] delegate() sort_threaded(int[] array)
> {
>     int[] result = array.dup;
> 
>     //Create and run a worker thread to perform an expensive operation, (in 
> this case a sort)
>     Thread tsort = new Thread(
>      {
>         result.sort;
>         return 0;
>      });
>     tsort.start();
> 
>     //The returned lambda delegate waits for the thread's calculation to 
> finish, then returns the result.
>     return { tsort.wait; return result; };
> }
> 
> In this situation, we can let the thread execute in the background while the 
> program performs other tasks, and only wait for the result once we need it. 
> This type of deferred calculation can be very useful for improving the 
> amount of parallelism within an application without adding much 
> synchronization overhead.  It is very sad that none of these examples work, 
> since there are so many nice solutions just like them.
> 
> One possible solution is to allocate the frame for each function containing 
> nested-functions on the heap.  This allows any returned delegates to use the 
> member variables freely.  One could think of it as translating the function 
> into a class with a thunk.  Here is above Fibonacci function rewritten in 
> this way:
> 
> class fibs_
> {
>     int a = 0;
>     int b = 1;
> 
>     int lambda1()
>     {
>         int c = a + b;
>         a = b;
>         b = c;
>         return c;
>     }
> 
>     int delegate() func()
>     {
>         return &lambda1;
>     }
> }
> 
> int delegate() fibs()
> {
>     return (new fibs_()).func();
> }
> 
> Such a translation is possible for any of these examples, albeit quite 
> tedious.  From what I gather C# applies a similar technique with its lambda 
> delegates and inner classes.  For a machine code compiler, such a rewrite is 
> not even necessary, since it could simply overwrite the frame pointer at the 
> start of the function with a GC allocated block.  This would preserve 
> frame-based addressing for arguments and variables, requiring no change in 
> any of the internally generated machine code.  The run time overhead is 
> fairly low, only on the order of one extra memory allocation per function 
> call.  Practically, such an implementation would be extremely simple.
> 
> Any thoughts or comments?
> 
> -Mikola Lysenko 
> 
> 


Yeah, this is a serious trap for the unwary and, unfortunately, 
prohibits the use of such delegates in the one place where the elegance 
would be most notable: as gui callbacks, per your example. As you say, 
the way around it (currently) is to create a class to house the 'scope' 
content, or otherwise refer to it. That's unweildy, even with 
anonymous-class syntax.

If, as you suggest, D had some means to indicate that the scope should 
be placed on the heap instead of the stack, it would resolve the concern 
nicely. Perhaps that indication might be lambda syntax itself? For 
example, in C# the lambda indicator is the symbol '=>'

Either way, if D could safely utilize lambda delegates for, say, gui 
work (or any design based around callbacks) it would be immediately 
noticable as both an elegant solution & a big step up from C++

Good idea, Mikola




More information about the Digitalmars-d mailing list