Generics 将不同的工作方式与涉及的泛型进行比较

Generics 将不同的工作方式与涉及的泛型进行比较,generics,f#,comparison,Generics,F#,Comparison,我偶然发现了一些“奇怪的行为”。我用F#interactive测试了一些代码并编写了 Seq.zip "ACT" "GGA" |> Seq.map ((<||) compare) // val it : seq<int> = seq [-1; -1; 1] let compute xs ys = Seq.zip xs ys |> Seq.map ((<||) compare) // val compute : xs:seq<'a> -> x

我偶然发现了一些“奇怪的行为”。我用F#interactive测试了一些代码并编写了

Seq.zip "ACT" "GGA" |> Seq.map ((<||) compare)
// val it : seq<int> = seq [-1; -1; 1]
let compute xs ys = Seq.zip xs ys |> Seq.map ((<||) compare)
// val compute : xs:seq<'a> -> xs:seq<'a> -> seq<int> when 'a : comparison
或者使用
符号
功能保持类型的泛型和组合

let compute (* ... *) ((<||) compare >> sign)
let compute(*…*)((>符号)

tl;dr问题是行为上的差异究竟来自何处?

这是F#编译器优化和.NET标准库优化之间复杂的相互作用

首先,F#努力优化您的程序。当类型在编译时已知,并且类型是原始的、可比较的,那么对
compare
的调用将被编译为直接比较。因此,比较示例中的字符就像
如果'A'<'G',那么-1 elif'A'>'G',那么1 elif'A'>'G'>

但是当你用泛型方法包装这个东西时,你会带走类型信息。这些类型现在是泛型的,编译器不知道它们是
char
。因此编译器被迫返回到调用
HashCompare.genericcomparisontinrinsic
,这反过来又调用
IComparable.CompareTo

现在猜猜
IComparable
是如何在
char
类型上实现的?它只需减去值并返回结果。认真地说,在C#中试试这个:

请注意,
IComparable
的这种实现在技术上并不是一个bug。根据,它不必只返回
[-1,0,+1]
,它可以返回任何值,只要其符号正确。我最好的猜测是,这也是为了优化

F#根本并没有具体说明这一点。它只是说“比较的结果”-想想应该是什么:-)


如果希望
compute
函数只返回
[-1,0,+1]
,可以通过使函数
内联来轻松实现:

let inline compute xs ys = Seq.zip xs ys |> Seq.map ((<||) compare)

让内联计算xs ys=Seq.zip xs ys |>Seq.map((回答很好。顺便说一句,如果C#impl是一个优化,而F#impl可以简单地重用它,这是否意味着F#one是主动次等的?回答很好;我查过
System.Char
是如何实现IComparable的,所以我知道减法值的。我不知道的信息大部分是第一部分。)(关于F#如何优化东西)。我想我会坚持增加对
符号的调用,至少这是一种明确的行为(没有明显的成本)@DaxFohl我想我必须同意F#实际上是次优的。但也许不是。减法肯定比使用条件分支跳转要短,但是减法还带有一个函数调用(
CompareTo
).所以我想说这是一个折腾。需要一个实验。顺便说一句,减法比分支更有效的原因之一是,使用减法,你永远不会遇到错误。请注意,我刚才链接的答案可能是所有堆栈溢出问题的最佳答案!如果你以前没有看过,一定要阅读它。即使你看过以前看过,再看一遍,看到一个问题回答得这么好,真是太高兴了。
let compute (* ... *) ((<||) compare >> sign)
Console.WriteLine( 'A'.CompareTo('G') ); // prints -6
let inline compute xs ys = Seq.zip xs ys |> Seq.map ((<||) compare)