2

I have been experimenting with Lambda expressions in Oxygene. Very simple recursive lambda expression to calculate a fibonacci number :

var fib : Func<int32, int32>;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);

When I run this code I get a nullreferenceexception. Any ideas as to what I'm doing wrong?

Steve
  • 6,382
  • 3
  • 41
  • 66
  • Beware, Robert's answer is mistaken and quite wrong. – Barry Kelly Nov 07 '08 at 19:24
  • So basically we are saying it is a bug, and until fixed we should avoid recursion in lambda expressions and anonymous methods. Would this cause problems elsewhere? – Steve Nov 07 '08 at 23:06
  • It is a bug. Don't know yet whether it'll be fixed before release, as apparently it's a lot of work. – Barry Kelly Nov 08 '08 at 03:28
  • 1
    It works in Delphi Prism 2010. I have inserted a working code sample as an answer. – Cary Jensen Sep 29 '09 at 08:38
  • Steve or Barry: someone who confirmed this as a bug in a specific version of Delphi should edit the question to specify. Cary's update makes this a version-specific issue. – Argalatyr Sep 29 '09 at 09:51

5 Answers5

5

You aren't doing anything wrong. If anything, the compiler should warn you about using fib, an unassigned variable, inside the body of the lambda.

However the compiler ought to be capturing fib as a location, so that when the assignment completes and the delegate is later invoked, fib is properly assigned and recursion should work as expected.

The most obvious possible reason for the failure is that Prism isn't capturing locations, but values, which would be grossly unintuitive and at odds with every other closure implementation in non-pure languages.

For example, try this code in JavaScript (contrary to Craig's assertion in the comments to this post, JavaScript also captures locations, not values):

<html>
<head>
<script language='javascript'>
function main()
{
    var x = 1;
    var f = function() { return x; };
    alert(f());
    x = 2;
    alert(f());
}
</script>
</head>
<body>
<input type=button onclick="javascript:main()"></input>
</body>
</html>

The alert boxes after you click on the button show 1 and 2 respectively, while following Prism/Oxygene semantics they would show 1 both times.

Barry Kelly
  • 41,404
  • 5
  • 117
  • 189
4

Steve:

The issue has apparently been addressed in Delphi Prism 2010. The following code sample works in the official release.

 var fib : Func<int32, int32>;
 fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
 var i := fib(9); //1,1,2,3,5,8,13,21,34
 MessageBox.Show(i.ToString);

The MessageBox shows the value 34.

In response to Jeroen's question, this code was run in the original, official release build, 3.0.21.661.

Cary Jensen
  • 3,751
  • 3
  • 32
  • 55
1

as a temporary workaround you can use:

var f := new class(f: Tfib := nil);
f.f := method(n : Int32): Int32
begin
  if n > 1 then  
    Result := f.f(n-1) + f.f(n-2)
  else
    Result := n;
end;
f.f(3);
Carlo Kok
  • 1,128
  • 5
  • 14
0

Prism handles the capture of local variables differently then native Delphi or C#. In those 2 all references in your code of those locals will be mapped to fields of the compiler generated class that will hold your anonymous method. In prism, these locals stay ordinary locals, yet the fields of this hidden fields are set when you instantiate the anonymous method.

One way to get a recursive lambda, would be to use a reference type to hold the lambda for you.

All of this sounds much more complicated then it really is.
2 methods of accomplishing your goal:
1)


    var fib := new class(Call : Func<Integer, Integer> := nil);  
    fib.Call := n -> iif(n > 1, fib.Call(n - 1) + fib.Call(n - 2), n);  
    var x := fib.Call(3);  

2)When you do not want to have a reference to this wrapper, you can do it like so:


    var fib : Func;  
    with fibWrapper := new class(Call : Func<Integer, Integer> := nil) do  
    begin  
        fibWrapper.Call := n -> iif(n > 1, fibWrapper.Call(n - 1) + fibWrapper.Call(n - 2), n);  
        fib := fibWrapper.Call;  
    end;

btw, the reason behind Prism not following C# here, is that for threading and loop, this reusing of captured vars makes for hard weird runtime problems. In Prism, captures are really captured the moment you assign the anonymous method or lambda. Which has a certain immuatble touch to it...

Cheers, Robert

Robert Giesecke
  • 4,314
  • 21
  • 22
  • 1
    Robert, the behaviour is a *bug* in Prism, and has been acknowledged as such by RemObjects. DO NOT take a dependency on this behaviour. And WRT loops and threading, shared state is EXACTLY what you want, otherwise you can't get your results out of the loop body! – Barry Kelly Nov 07 '08 at 19:24
  • Well, yes. They have to make thise working without the need of the outer anonmyous class. However, having a way to call an anonymous method/lambda recursively does not necessarily mean, they'd have to reuse the helper target of the delegate. That'd be **bad**, IMO. – Robert Giesecke Nov 08 '08 at 10:38
0

Does the same apply to Anonymous Methods? I'm guessing it does, but can't quite figure out the syntax to get this to run

  var f : Tfib;
  f := method(n : Int32): Int32
  begin
    if n > 1 then  
      Result := f(n-1) + f(n-2)
    else
      Result := n;
  end;

Edit

It does.

  var f := new class(call : TFib := nil);
  f.call := method(n : Int32): Int32
  begin
    if n > 1 then  
      Result := f.call(n-1) + f.call(n-2)
    else
      Result := n;
  end;
Steve
  • 6,382
  • 3
  • 41
  • 66