Lambda-Ausdrücke in Delphi Prism / Oxygene
Frage
Ich habe mit Lambda-Ausdrücke in Oxygene experimentiert. Sehr einfacher Ausdruck rekursive Lambda eine Fibonacci-Zahl zu berechnen:
var fib : Func<int32, int32>;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);
Wenn ich diesen Code ausführen bekomme ich eine Nullreferenceexception. Alle Ideen, was ich falsch mache?
Lösung
Sie tun nichts falsch gemacht. Wenn überhaupt, sollte der Compiler Sie warnen vor fib verwenden, eine freie Variable, im Inneren des Körpers des Lambda.
Allerdings sollte die Compiler fib als Standort zu erfassen, so dass, wenn die Zuordnung abgeschlossen ist und die Delegierten später aufgerufen wird, wird fib richtig zugewiesen und Rekursion sollte wie erwartet.
Der naheliegendste mögliche Grund für das Scheitern ist, dass Prism nicht erfassen Standorte, sondern Werte, die grob unintuitive und mit jeder anderen Verschluss Umsetzung in nicht-reinen Sprachen im Widerspruch sein würde.
Zum Beispiel, versuchen Sie diesen Code in JavaScript (im Gegensatz zu Craig der Behauptung in den Kommentaren zu diesem Beitrag JavaScript erfaßt auch Standorte, nicht Werte):
<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>
Die Alertbox nach dem Klicken auf die Schaltfläche zeigen 1 bzw. 2, während im Anschluss an Prism / Oxygene Semantik sie 1 beide Male zeigen würde.
Andere Tipps
Steve:
Das Problem hat sich offenbar in Delphi Prism 2010 Das folgende Codebeispiel arbeitet in der offiziellen Veröffentlichung angesprochen.
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);
Die MessageBox zeigt den Wert 34.
Als Reaktion auf Jeroen Frage, dieser Code wurde in dem ursprünglichen, offiziellen Release-Build ausführen, 3.0.21.661.
als vorübergehende Lösung Sie verwenden können:
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);
Prism übernimmt die Erfassung von lokalen Variablen anders dann nativen Delphi oder C #. In diesen 2 alle Verweise im Code dieser Einheimischen werden auf Feldern der Compiler generierten Klasse zugeordnet werden, die Ihre anonyme Methode halten wird. In Prisma, bleiben diese Einheimischen gewöhnlichen Einheimischen, doch sind die Felder dieses verborgenen Felder gesetzt, wenn Sie die anonyme Methode instanziiert.
Eine Möglichkeit, eine rekursive Lambda zu bekommen, wäre einen Referenztyp zu verwenden, um das Lambda für Sie zu halten.
Das alles klingt viel komplizierter dann es wirklich ist.
2 Methoden zum Erreichen Sie Ihr Ziel:
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) Wenn Sie nicht möchten, einen Verweis auf diese Wrapper haben, können Sie es tun, wie 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, der Grund für Prism nicht hier folgenden C # ist, dass zum Einfädeln und Schleife, diese Wiederverwendung von gefangenen Vars für harte seltsame Laufzeit Probleme macht. In Prism, werden erfaßt Aufnahmen wirklich, sobald Sie die anonyme Methode oder Lambda zuweisen. Die eine gewisse immuatble Note hat ...
Cheers, Robert
Gilt das auch für Anonyme Methoden anwenden? Ich vermute, es tut, kann aber durchaus nicht die Syntax herauszufinden, bekommen dies zu laufen
var f : Tfib;
f := method(n : Int32): Int32
begin
if n > 1 then
Result := f(n-1) + f(n-2)
else
Result := n;
end;
Bearbeiten
Es tut.
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;
Ich habe versucht, die Zuordnung auch die Variable:
var fib : Func<int32, int32> := nil;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);
Immer noch ohne Glück.
Aus Neugier habe ich versucht, etwas ähnliches anonyme Methoden verwenden.
Im Prinzip implementiert ich eine Tiefensuche auf einem direkten azyklischen Graphen eine rekursive anonyme Methode:
var dfs : dfsmethod;
dfs := method(Vertex : IVertex)
begin
var IsDone : Boolean;
Visited[Vertex.Key] := True;
aMethod(Vertex.Key, Vertex.Weight, var IsDone); //PreVisit
if IsDone then Exit;
for each Successor in Vertex.Successors do
if not Visited[Successor.Key] then
dfs(Successor);
end;
dfs(InternalGetVertex(aStart));
Das zusammengestellt, aber ich habe den gleichen Fehler. Nullreferenceexception.
Ich habe auch versucht, um die Fibonacci als rekursive anonyme Methode neu zu implementieren:
var f : Tfib;
f := method(n : Int32): Int32
begin
if n > 1 then
Result := f(n-1) + f(n-2)
else
Result := n;
end;
f(3)
wieder gleiches Problem! Immer auf der zweiten Iteration (das heißt ersten rekursiven Aufruf)