比较与泛型有关的工作方式

Seh*_*cht 13 generics comparison f#

我偶然发现了一些"奇怪的行为".我使用F#interactive来测试一些代码并编写

Seq.zip "ACT" "GGA" |> Seq.map ((<||) compare)
// val it : seq<int> = seq [-1; -1; 1]
Run Code Online (Sandbox Code Playgroud)

然后我想用它制作一个函数并写下来

let compute xs ys = Seq.zip xs ys |> Seq.map ((<||) compare)
// val compute : xs:seq<'a> -> xs:seq<'a> -> seq<int> when 'a : comparison
Run Code Online (Sandbox Code Playgroud)

这推广了第一段代码,我认为这是件好事......直到我尝试使用它

compute "ACT" "GGA"
// val it : seq<int> = seq [-6; -4; 19]
Run Code Online (Sandbox Code Playgroud)

因此,compare当存在不同的"观点"(显式类型与泛型)时,以某种方式对"相同的事物"采取不同的行为

我知道如何解决它:要么通过明确的类型

let compute (xs: #seq<char>) // ... or char seq or string
Run Code Online (Sandbox Code Playgroud)

或者保持类型通用并与sign函数组合

let compute (* ... *) ((<||) compare >> sign)
Run Code Online (Sandbox Code Playgroud)

tl; dr问题是行为的差异究竟来自哪里?

Fyo*_*kin 11

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

首先,F#努力优化您的程序.当类型在编译时已知,并且类型是原始的和可比较的,那么调用将compare被编译为直接比较.因此,比较示例中的字符会是这样的if 'A' < 'G' then -1 elif 'A' > 'G' then 1 else 0.

但是当你用通用方法包装东西时,你会带走类型信息.这些类型现在是通用的,编译器不知道它们是什么char.因此编译器被迫回退到调用HashCompare.GenericComparisonIntrinsic,调用又调用IComparable.CompareTo参数.

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

Console.WriteLine( 'A'.CompareTo('G') ); // prints -6
Run Code Online (Sandbox Code Playgroud)

请注意,此类实现在IComparable技术上不是错误.根据文档,它不必仅[-1,0,+1]返回,只要其符号正确,它就可以返回任何值.我最好的猜测是,这也是为了优化.

F#文档compare根本没有指定.它只是说"比较的结果" - 去看看应该是什么:-)


如果您希望compute函数只返回[-1,0,+1],可以通过以下函数轻松实现inline:

let inline compute xs ys = Seq.zip xs ys |> Seq.map ((<||) compare)
Run Code Online (Sandbox Code Playgroud)

现在它将在调用站点进行扩展,其中类型已知,并且可以插入优化的代码.但请记住,由于[-1,0,+1]文档中的行为无法保证,因此将来可能会消失.所以我宁愿不依赖它.