Delphi Prism / Oxygene中的Lambda表达式
题
我一直在试验Oxygene中的Lambda表达式。非常简单的递归lambda表达式来计算斐波那契数:
var fib : Func<int32, int32>;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);
当我运行此代码时,我得到一个nullreferenceexception。关于我做错了什么的想法?
解决方案
你没有做错任何事。如果有的话,编译器应该警告你在lambda体内使用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#不同。 在这些本地的代码中,所有引用中的所有引用都将映射到将保存匿名方法的编译器生成的类的字段。 在棱镜中,这些本地人保持普通的本地人,但是当您实例化匿名方法时,会设置隐藏字段的字段。
获取递归lambda的一种方法是使用引用类型为你保存lambda。
所有这些听起来都要复杂得多。
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中,捕获是在分配匿名方法或lambda的那一刻捕获的。它有一定的不可触摸的触摸......
干杯, 罗伯特
这同样适用于匿名方法吗?我猜它确实如此,但无法弄清楚要运行的语法
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)
再次出现同样的问题!始终在第二次迭代(即第一次递归调用)