Oxygene でラムダ式を試しています。フィボナッチ数を計算するための非常に単純な再帰ラムダ式:
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);
メッセージボックスには値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;
ところで、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;