在OCaml中,拥有.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#非常熟悉的人可以指出描述这种情况的参考文件,我将不胜感激。)
解决方案是系统地将括号添加到{em>所有 .mli
中的函数类型,但我担心这会导致效率低下。另一种方法是检测计算出的函数,并在.mli
中添加括号大小的相应类型。第三种解决方案是η-扩展明显的案例,并将其他案例加以括号。
我对F#/ CLR内部结构不够熟悉,无法衡量哪些会产生显着的性能或接口处罚。
答案 0 :(得分:8)
理论上,F#函数类型'a -> 'b -> 'c
与'a -> ('b -> 'c)
的类型相同。也就是说,使用F#中的curried表示多个参数函数。在大多数情况下,您可以使用另一个预期的对象,例如在调用高阶函数时。
但是,出于实际原因,F#编译器实际上区分了类型 - 动机是它们在编译的.NET代码中表示不同。这对性能以及与C#的互操作性有影响,因此有必要进行区分。
函数Foo : int -> int -> int
将被编译为成员int Foo(int, int)
- 编译器默认情况下不使用curried表单,因为在使用两个参数调用Foo
时这更有效(更常见的情况),互操作更好。函数Bar : int -> (int -> int)
将被编译为FSharpFunc<int, int> Bar(int)
- 实际上使用curried形式(因此使用单个参数调用它更有效,并且很难从C#中使用它)。
这也是为什么F#在签名时不会将类型视为相等的原因 - 签名指定了类型,但在这里它还指定了如何编译函数。实现文件必须提供正确类型的函数,但是 - 在这种情况下 - 也是正确的编译形式。
答案 1 :(得分: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)
一切正常