Generics 将不同的工作方式与涉及的泛型进行比较
我偶然发现了一些“奇怪的行为”。我用F#interactive测试了一些代码并编写了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
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)