我一直在尝试在 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# 不同。在这两个本地代码中的所有引用都将映射到编译器生成的类的字段,该类将保存您的匿名方法。在 prism 中,这些 locals 仍然是普通的 locals,但是这个隐藏字段的字段是在您实例化匿名方法时设置的。
获得递归 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;