13

在 OCaml 中,拥有 in 是合法的.mli

val f : 'a -> 'a
val g : 'a -> 'a

.ml

let f x = x
let g = f

然而在 F# 中,这被拒绝了:

eta_expand.ml(2,5): error FS0034: Module 'Eta_expand' contains
    val g : ('a -> 'a)    
but its signature specifies
    val g : 'a -> 'a    

The arities in the signature and implementation differ. The signature specifies that 'g' is function definition or lambda expression accepting at least 1 argument(s), but the implementation is a computed function value. To declare that a computed function value is a permitted implementation simply parenthesize its type in the signature, e.g.
    val g: int -> (int -> int)
instead of
    val g: int -> int -> int.

一种解决方法是对 g 的定义进行 η 扩展:

let g x = f x

如果我的代码是纯功能性的(没有例外,没有副作用等),这应该是等效的(实际上,多态性可能会更好,这取决于语言如何概括类型:在 OCaml 中,部分应用程序不会产生多态性函数,但它们的 η-展开确实)。

系统的 η 展开有什么缺点吗?

两个答案避开了关于 η-expansion 的问题 :-) 而是建议我在我的函数类型周围添加括号。这是因为,显然,F# 在类型级别区分函数的“真实”定义(作为 λ 表达式和计算定义,如在部分应用程序中);这大概是因为 λ 表达式直接映射到 CLR 函数,而计算定义映射到委托对象。(我不确定这种解释,如果非常熟悉 F# 的人可以指出描述这一点的参考文档,我将不胜感激。)

一个解决方案是系统地为 中的所有函数类型添加括号.mli,但我担心这会导致效率低下。另一种方法是检测计算函数并在.mli. 第三种解决方案是将 η-展开明显的情况,并将其他情况用括号括起来。

我对 F#/CLR 内部结构不够熟悉,无法衡量哪些会导致显着的性能或接口损失。

4

2 回答 2

10

理论上,F# 函数类型'a -> 'b -> 'c'a -> ('b -> 'c). 也就是说,在 F# 中使用柯里化形式表示多个参数函数。在大多数情况下,例如在调用高阶函数时,您可以使用其中一个。

但是,出于实际原因,F# 编译器实际上会区分这些类型 - 动机是它们在编译的 .NET 代码中以不同的方式表示。这会影响性能以及与 C# 的互操作性,因此进行区分很有用。

函数Foo : int -> int -> int将被编译为成员int Foo(int, int)- 默认情况下编译器不使用柯里化形式,因为这在Foo使用两个参数(更常见的情况)调用时效率更高,并且更适合互操作。函数Bar : int -> (int -> int)将被编译为FSharpFunc<int, int> Bar(int)- 实际上使用柯里化形式(因此仅使用单个参数调用它会更有效,并且很难从 C# 中使用)。

这也是为什么 F# 在涉及签名时不将类型视为相等的原因 - 签名指定类型,但在这里它还指定如何编译函数。实现文件必须提供正确类型的函数,但在这种情况下,也必须提供正确的编译形式。

于 2013-04-05T11:19:18.997 回答
4

有趣的是我fsi给出了一个更有帮助的错误信息:

/test.fs(2,5): error FS0034: Module 'Test' contains
    val g : ('a -> 'a) but its signature specifies
    val g : 'a -> 'a The arities in the signature and implementation differ. 
          The signature specifies that 'g' is function definition or lambda expression 
          accepting at least 1 argument(s), but the implementation is a computed 
          function value. To declare that a computed function value is a permitted 
          implementation simply parenthesize its type in the signature, e.g.
        val g: int -> (int -> int) instead of
        val g: int -> int -> int.

如果您添加括号以获取g :('a -> 'a)所有内容都可以

于 2013-04-05T09:04:06.297 回答