Delphi Prism / Oxygeneのラムダ式
質問
OxygeneでLambda式を試しています。フィボナッチ数を計算するための非常に単純な再帰ラムダ式:
var fib : Func<int32, int32>;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);
このコードを実行すると、nullreferenceexceptionが発生します。私が間違っていることに関するアイデアはありますか?
解決
あなたは何も悪いことをしていない。どちらかといえば、コンパイラは、ラムダの本体内で未割り当ての変数fibを使用することについて警告する必要があります。
ただし、コンパイラはfibをロケーションとしてキャプチャする必要があるため、割り当てが完了してデリゲートが後で呼び出されると、fibが適切に割り当てられ、再帰が期待どおりに動作するはずです。
失敗の最も明白な考えられる理由は、Prismが場所をキャプチャするのではなく、非常に直感的でなく、純粋でない言語での他のすべてのクロージャー実装と対立する値をキャプチャすることです。
たとえば、JavaScriptでこのコードを試してください(この投稿へのコメントでのCraigの主張に反して、JavaScriptは値ではなく場所もキャプチャします):
<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>
ボタンをクリックした後のアラートボックスはそれぞれ1と2を表示しますが、Prism / Oxygeneのセマンティクスに従って、両方とも1を表示します。
他のヒント
スティーブ:
この問題はDelphi Prism 2010で解決されたようです。次のコードサンプルは公式リリースで機能します。
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);
MessageBoxには値34が表示されます。
Jeroenの質問に応えて、このコードはオリジナルの公式リリースビルド3.0.21.661で実行されました。
使用できる一時的な回避策として:
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は、ネイティブDelphiまたはC#とは異なる方法でローカル変数のキャプチャを処理します。 これらの2つでは、これらのローカルのコード内のすべての参照は、匿名メソッドを保持するコンパイラー生成クラスのフィールドにマップされます。 プリズムでは、これらのローカルは通常のローカルのままですが、匿名メソッドをインスタンス化すると、この非表示フィールドのフィールドが設定されます。
再帰的なラムダを取得する1つの方法は、参照型を使用してラムダを保持することです。
これらはすべて、実際よりもはるかに複雑に聞こえます。
目標を達成する2つの方法:
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)このラッパーへの参照が必要ない場合は、次のように実行できます。
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、ここでPrismがC#に続かない理由は、スレッド化とループのために、キャプチャされた変数を再利用することで、実行時に問題が発生するためです。 Prismでは、匿名メソッドまたはラムダを割り当てた瞬間にキャプチャが実際にキャプチャされます。それは特定の不変のタッチを持っています...
乾杯、 ロバート
匿名メソッドにも同じことが適用されますか?私はそれを推測していますが、これを実行するための構文を理解することはできません
var f : Tfib;
f := method(n : Int32): Int32
begin
if n > 1 then
Result := f(n-1) + f(n-2)
else
Result := n;
end;
編集
それは。
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;
変数の割り当ても試みました:
var fib : Func<int32, int32> := nil;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);
まだ運がありません。
好奇心から、匿名メソッドを使用して同様のことを試しました。
基本的に、再帰的な匿名メソッドを使用して、直接非循環グラフで深さ優先検索を実装しました:
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));
これはコンパイルされましたが、同じエラーが発生しました。 NullReferenceException。
また、フィボナッチを再帰的な匿名メソッドとして再実装しようとしました:
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)
再び同じ問題!常に2回目の繰り返し(つまり、最初の再帰呼び出し)