The future of lambda delegates
Lars Ivar Igesund
larsivar at igesund.net
Wed Aug 16 08:22:50 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
I agree that the current spec allows you to very easily write very elegant
code that will be broken. Your proposed solution seems to me to be a well
thought out (and acceptable) solution to make them actually work (and keep
D as a language ahead of the pack, as the other alternatives most probably
are to either create not-so-elegant syntax, or disallow them altogether).
--
Lars Ivar Igesund
blog at http://larsivi.net
DSource & #D: larsivi
More information about the Digitalmars-d
mailing list