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]文档中的行为无法保证,因此将来可能会消失.所以我宁愿不依赖它.