我仍然试图围绕F#如何概括(或不是)函数和类型,并且有一个案例让我烦恼:
let min(a, b) = if a < b then a else b
let add(a, b) = a + b
let minInt = min(3, 4)
let minFloat = min(3.0, 4.0) // works!
let addInt = add(3, 5)
let addFloat = add(3.0, 5.0) // error: This expression was expected to have type
// int but here has type float
这里min具有泛型类型'a * 'a -> 'a (requires comparison)
,而add具有一个具体类型int * int -> int
,显然是从它在程序中的第一次使用推断出来的。两者都以相同的方式声明和使用,那么为什么推广的区别呢?
我理解在add的情况下,通过声明函数inline来解决问题,这会导致它获得泛型类型定义,即'a * 'b -> 'c (requires member (+))
,但这并不能解释为什么这样做在这种情况下需要而不是另一种。
答案 0 :(得分:7)
@TomasP在这个问题上有一篇很好的文章:http://tomasp.net/blog/fsharp-generic-numeric.aspx
当编写具有某些类型参数'T的简单通用代码时,我们 什么都不知道类型参数,没有办法 将它限制为一个数字类型,提供我们所有的运算符 可能需要在我们的代码中使用。这是.NET运行时的限制 而F#提供了两种克服它的方法。
但为什么<
和>
(以及=
,<=
和>=
)为什么好?
F#编译器对equality
和comparison
的处理方式不同(请参阅specs中的5.2.10等式和比较约束一节,感谢@Daniel)。您获得特殊 comparison
约束,这是允许的(简单地,请参阅规范以获取更多详细信息):
如果类型是命名类型,则类型定义不具有, 并且不推断具有NoComparison属性和类型 definition实现System.IComparable或者是数组类型或者是 System.IntPtr或是System.UIntPtr。
+
运营商没有这种特殊处理方式。为什么不存在诸如numeric
?
那么运营商是否也为字符串定义了?在某些语言中列表和集合?肯定会是addable
约束而不是numeric
。然后可以在具有不同语义含义的程序中找到许多这样的重载运算符。所以F#提供了一个带有静态成员约束和inline关键字的'catch-all'方法。只有equality
和comparison
是特殊的。
答案 1 :(得分:2)
为何泛化的区别?
在一般性和表现之间存在权衡。 comparison
约束为诸如min
之类的函数提供了通用性,这些函数可以对任何F#类型的任何值起作用,但在一般情况下它会转向虚拟分派并且慢很多倍。 +
运算符提供了有限的通用性,并且可以对任何已经过载扩充但在一般情况下不起作用的F#类型的任何值起作用,因此总是非常快,因为永远不需要调度。 / p>
答案 2 :(得分:1)
comparison
是编译时约束。所以问题仍然存在:为什么不add
一般化?正如yamen指出的那样,泛型数学操作需要内联,这会影响代码大小和性能 - 可能不是编译器应该自动执行的操作。