【问题标题】:Why does functions in F# implemented in such a way?为什么 F# 中的函数会以这种方式实现?
【发布时间】:2013-02-17 23:40:08
【问题描述】:

由于支持柯里化,F# 函数与普通 CLR 方法有很大不同。例如函数

let inc a = a + 1

类型为Microsoft.FSharp.Core.FSharpFunc<int,int>。它会产生 C# 互操作性问题。必须专门设计函数以便从 C# 中轻松调用。

这种设计背后的基本原理是什么?我相信原因是咖喱支持。但是柯里化可以使用闭包来实现。例如这段代码:

let add a b = a + b
let inc = add 1

可以很容易地被编译器转换成这个:

let add a b = a + b
let inc = fun x -> add 1 + x

在这种情况下 add 和 inc 都可以是普通的System.Func 对象。我相信这个设计决定背后有一些有趣的原因。

【问题讨论】:

  • 向 Don Syme / F# 团队的其他人发送电子邮件可能会更好。虽然我有理由确定整个原因是为了柯里化,但对于更复杂的情况来说,这可能不是那么简单。
  • 我相信 Don Syme 和其他 F# 团队成员也是 SO 用户。
  • Don Syme 不是,但 Tomas Petricek 是,Brian 也是 F# 团队的一员(我认为)。
  • @JohnPalmer 我很确定 Keith B. 也在这里。

标签: f#


【解决方案1】:

据我所知,在 F# 中为函数提供单独类型的动机是性能(在早期版本中,FSharpFunc<...> 实际上称为FastFunc<...>)。我不完全确定最近的发展(我确信 F# 团队做了一些测试来确定代理是否可以在 Visual Studio 2010 中工作),但我是这样理解这个问题的:

如果您有一个函数add : int -> int -> int,那么该函数可以表示为一个委托Func<int, Func<int, int>>(使用柯里化表示)。问题是您经常需要使用add 1 2 这样的两个参数来调用它。

使用嵌套Func 类型的表示,这将编译为add.Invoke(1).Invoke(2)

但是,当编译像add 这样的函数时,F# 编译器实际上会创建一个新类,例如,AddClass,它继承来自FSharpFunc<int, FSharpFunc<int, int>>并且添加了一个带有两个参数的附加 Invoke 重载。这意味着,在大多数情况下,add 1 2 可以编译为一个调用 add.Invoke(1, 2)

这种设计使 F# 代码更快。它使互操作性略微复杂化,但不会太多。编写一个接受委托的 F# 成员或函数相当容易:

let foo (inc : Func<int, int>) = inc.Invoke(41)

(您只需要添加类型注释,然后调用f.Invoke - 但您也可以使用f.Inokve 作为一等值并将其传递给其他函数)

【讨论】:

  • 另外值得注意的是,F# 支持面向 .NET 2.0,但 System.Func&lt;&gt;System.Action&lt;&gt; 仅在 .NET 3.5 中引入。
  • 哇,这是非常有趣的实现细节。谢谢!
  • 这是 F# 函数更快的另一个原因:CLI 操作/函数继承自 System.MulticastDelegate。它们在被调用时会迭代一个内部调用列表,而 F# 函数则不会。
【解决方案2】:

我的猜测是 F# 表示支持大步语义,而 System.Func 不支持。

【讨论】:

  • 什么是“大步语义”?我想这是某种函数聚合,当编译器可以从较小的函数中创建大函数时。对吗?
  • 是的。您不想在每次应用另一个柯里化参数时创建临时闭包,因此您会发现何时可以一次应用多个柯里化参数并跳过临时参数,迈出“一大步”。 OCaml 推广了这一点,而标准 ML 倾向于避免柯里化。
【解决方案3】:

与 C# 互操作的最佳方法是将所有内容包装在类/成员中。 C# 永远不必查看成员的内部工作原理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 2018-11-28
    • 1970-01-01
    • 1970-01-01
    • 2018-01-01
    • 1970-01-01
    相关资源
    最近更新 更多