F#中的通用函数

9

我还在努力理解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 (需要比较),而add具有具体类型int * int -> int,显然是从程序中第一次使用中推导出来的。两者的声明和使用方式相同,那么为什么会存在归纳的差异呢?
我了解到,在add的情况下,可以通过声明函数为inline来避免问题,这会导致它获得一个通用类型定义,即'a * 'b -> 'c (需要成员(+)),但这并不能解释为什么这种情况需要而另一种情况不需要。

6
编译器已将“可比较”的概念抽象化,但它并没有将“可相加”的概念抽象化。 在这里,“<”是不同寻常的,而不是“+”。 - ildjarn
你为什么期望使用 +< 生成相同的签名? - Daniel
@Daniel 我并不期望它们有相同的签名(尽管在这种特定情况下这是有意义的,但这不是重点);我期望它们要么都是泛型的,要么都不是。在这里,一个是具体的,另一个则没有明显的理由是泛型的。 - Asik
请参考以下链接了解背景信息:https://dev59.com/l2PVa4cB1Zd3GeqP9dMm#10588667 - yamen
3个回答

7

这里有一篇关于此问题的优秀文章,作者是@TomasP,链接在此:http://tomasp.net/blog/fsharp-generic-numeric.aspx

当编写简单的泛型代码时,我们对类型参数'T一无所知,也没有办法将其限制为数字类型,并提供我们可能需要在代码中使用的所有运算符。这是.NET运行时的限制,F#提供了两种克服它的方法。

但是为什么<>(以及扩展的=<=>=)没问题呢?

F#编译器对equalitycomparison的处理方式不同(请参见specs中的5.2.10节Equality and Comparison Constraints,感谢@Daniel)。您将获得特殊的comparison约束条件,当(简单地说,请查看规范以获取更多详细信息):

如果类型是命名类型,则类型定义没有并且未被推断为具有NoComparison属性,并且类型定义实现了System.IComparable或是数组类型或是System.IntPtr或是System.UIntPtr。

+运算符没有这样的特殊处理。为什么不能有像numeric这样的约束条件呢?

那么,这个操作符对字符串也定义了吗?在某些语言中,它还适用于列表和集合?肯定它应该是一个“可添加”的约束而不是“数值”约束。然后,程序中可以找到许多具有不同语义含义的这种重载运算符。因此,F#提供了一个使用静态成员约束和inline关键字的“万能”方法。只有“相等性”和“比较”是特殊的。


通常情况下,F#会尽可能地将类型定义为在使用时最为通用的。然而,它无法欺骗.NET框架并为“数字”这个概念定义一个泛型 - 因为没有相关的接口可以约束它! - yamen
2
@yamen:comparison 约束在 C# 中不会生成类似 where T : IComparable 的内容。它仅存在于编译时,并且限制较少。请参阅规范的相关部分 - Daniel
是的,这有点不同 - 但从规范来看,“它通常意味着类型必须实现System.IComparable”。我会进行更新以确保准确性。 - yamen
我提到这个是因为它似乎反驳了你的回答的主旨:即.NET中固有的比较而数学运算不是。你的回答并没有给出差异的理由(我不明白为什么不能有像“需要数字”这样的东西),只是解释了不同的实现机制。 - Daniel
我不知道答案,但我怀疑通用数学操作主要是为了性能而内联的,约束只是副产品。 - Daniel
显示剩余2条评论

2
为什么有泛化的差异?
在通用性和性能之间存在权衡。比较约束为像min这样可以作用于任何F#类型的任何值的函数提供了通用性,但在一般情况下会采用虚拟分派,因此速度会慢很多。加号运算符提供了受限制的通用性,并且可以作用于已增强重载的任何F#类型的任何值,但在一般情况下不起作用,因此始终非常快,因为不需要分派。

1

comparison是一个编译时的约束条件。因此问题仍然存在:为什么没有将add泛化?正如yamen所指出的,泛型数学操作需要内联,这会影响代码大小和性能-这可能不是编译器自动执行的东西。


影响代码大小,但对性能有积极的影响。我不知道是否存在其他权衡。 - yamen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接