【问题标题】:Lambda Expressions in Delphi Prism/OxygeneDelphi Prism/Oxygene 中的 Lambda 表达式
【发布时间】:2008-11-06 21:26:48
【问题描述】:

我一直在 Oxygene 中试验 Lambda 表达式。计算斐波那契数的非常简单的递归 lambda 表达式:

var fib : Func<int32, int32>;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);

当我运行这段代码时,我得到一个空引用异常。关于我做错了什么有什么想法吗?

【问题讨论】:

  • 当心,罗伯特的回答是错误的,而且是完全错误的。
  • 所以基本上我们说这是一个错误,在修复之前我们应该避免在 lambda 表达式和匿名方法中递归。这会在其他地方引起问题吗?
  • 这是一个错误。还不知道它是否会在发布前修复,因为显然这是很多工作。
  • 它在 Delphi Prism 2010 中工作。我插入了一个工作代码示例作为答案。
  • 史蒂夫或巴里:确认这是特定版本的 Delphi 中的错误的人应该编辑问题以指定。 Cary 的更新使这是一个特定于版本的问题。

标签: delphi lambda fibonacci oxygene


【解决方案1】:

你没有做错任何事。如果有的话,编译器应该警告你在 lambda 的主体内使用 fib,一个未分配的变量。

但是编译器应该将 fib 捕获为一个位置,以便当分配完成并且稍后调用委托时,fib 被正确分配并且递归应该按预期工作。

失败的最明显可能原因是 Prism 不是捕获位置,而是捕获值,这非常不直观,并且与非纯语言中的所有其他闭包实现不一致。

例如,在 JavaScript 中尝试这段代码(与 Craig 在这篇文章的 cmets 中的断言相反,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 两次。

【讨论】:

  • 好吧,JavaScript 就是这样工作的(捕获值),而且它远非纯粹!但我同意 .NET 语言不应该。
  • 如果您按照 Robert 的示例,并且不分配变量,那么您现在会按预期收到警告。
  • 克雷格,JavaScript 捕获 位置,而不是值。
  • Steve,代码在明确赋值之前使用了变量,C#会针对示例代码中的具体情况报错。
【解决方案2】:

史蒂夫:

该问题显然已在 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 中运行的。

【讨论】:

  • 你能编辑你的答案并指出它被修复的版本号吗(你使用的是 Delphi 2010 的发布版本还是 udpate 1?)
【解决方案3】:

作为临时解决方法,您可以使用:

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);

【讨论】:

    【解决方案4】:

    Prism 处理本地变量的捕获方式与原生 Delphi 或 C# 不同。 在这两个本地代码中的所有引用都将映射到编译器生成的类的字段,该类将保存您的匿名方法。 在 prism 中,这些局部变量仍然是普通局部变量,但是这些隐藏字段的字段是在您实例化匿名方法时设置的。

    获得递归 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 的那一刻真正被捕获。哪个有一定的不变性...

    干杯, 罗伯特

    【讨论】:

    • Robert,该行为是 Prism 中的一个 错误,RemObjects 已确认此行为。不要依赖这种行为。 WRT 循环和线程,共享状态正是你想要的,否则你无法从循环体中得到结果!
    • 嗯,是的。他们必须使 thise 在不需要外部匿名类的情况下工作。但是,有一种递归调用匿名方法/lambda 的方法并不一定意味着,他们必须重用委托的帮助器目标。那将是糟糕,IMO。
    【解决方案5】:

    这同样适用于匿名方法吗?我猜是这样,但不能完全弄清楚让它运行的语法

      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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-13
      相关资源
      最近更新 更多