考虑代码两个片段,简单地订购分别C#
和F#
字符串:
C#:
var strings = new[] { "Tea and Coffee", "Telephone", "TV" };
var orderedStrings = strings.OrderBy(s => s).ToArray();
F#:
let strings = [| "Tea and Coffee"; "Telephone"; "TV" |]
let orderedStrings =
strings
|> Seq.sortBy (fun s -> s)
|> Seq.toArray
这两个代码片段返回不同的结果:
在我的特定情况下,我需要这两种语言(其中一个是生产代码,一个是测试断言的一部分)之间的次序的逻辑关联。这就提出了几个问题:
编辑
在回答几个探测意见,运行碎片下面揭示更多关于这个排序的差异的确切性质:
F#:
let strings = [| "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" |]
let orderedStrings =
strings
|> Seq.sortBy (fun s -> s)
|> Seq.toArray
C#:
var strings = new[] { "UV", "Uv", "uv", "uV", "TV", "tV", "Tv", "tv" };
var orderedStrings = strings.OrderBy(s => s).ToArray();
得到:
字符串的词典式排序不同,因为在字符的基本顺序的差异:
见language spec第8.15.6。
字符串,数组和本地整数有特殊的比较语义,一切只是去,如果这是实现(即产生相同的结果模各种优化)到IComparable
。
特别是,F#字符串使用默认序号比较,而相比之下,大多数.NET的使用由默认的文化意识比较。
这显然是F#和其他.NET语言之间的不兼容的混乱,但它确实有一些好处:
Comparer<string>.Default.Compare("a", "A") // -1
C#Comparer<char>.Default.Compare('a', 'A') // 32
F#compare "a" "A" // 1
F#compare 'a' 'A' // 32
编辑:
需要注意的是它的误导性(尽管不是不正确的)声明,“F#使用区分大小写字符串比较”。 F#使用序号比较,这不仅仅是区分大小写严格。
// case-sensitive comparison
StringComparer.InvariantCulture.Compare("[", "A") // -1
StringComparer.InvariantCulture.Compare("[", "a") // -1
// ordinal comparison
// (recall, '[' lands between upper- and lower-case chars in the ASCII table)
compare "[" "A" // 26
compare "[" "a" // -6
不同的库就串默认比较操作的不同选择。 F#是严格默认为区分大小写,而LINQ到对象是不区分大小写的。
无论List.sortWith
和Array.sortWith
允许指定的比较。由于不Enumerable.OrderBy
的过载。
然而Seq
模块不会出现具有等同的(和一个没有在4.6添加)。
对于具体的问题:
是否有在订货逻辑差异的根本原因?
这两种排序都是有效的。在英语的情况下不敏感似乎更自然,因为这是我们已经习惯了。但是,这并不使之更正确。
什么是我的情况,以解决这个“问题”是推荐的方式?
要明确的那种比较。
这种现象特定字符串,或是否适用于其他.NET类型呢?
char
也将受到影响。和任何其他类型那里有不止一个可能的排序(如People
类型:您可以通过名称或视具体要求出生日期顺序)。
这无关用C#VS F#,甚至IComparable
,但仅仅是因为在图书馆不同的排序实现。
该TL; DR;版本是字符串进行排序会产生不同的结果:
"tv" < "TV" // false
"tv".CompareTo("TV") // -1 => implies "tv" *is* smaller than "TV"
或者更清楚:
"a" < "A" // false
"a".CompareTo("A") // -1 => implies "a" is smaller than "A"
这是因为CompareTo
使用当前文化(see MSDN)。
我们可以看到这是如何发挥出来在实践中一些不同的例子。
如果我们使用标准的F#的排序,我们得到的大写字母,第一个结果:
let strings = [ "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" ]
strings |> List.sort
// ["TV"; "Tv"; "UV"; "Uv"; "tV"; "tv"; "uV"; "uv"]
即使我们投来IComparable
我们得到相同的结果:
strings |> Seq.cast<IComparable> |> Seq.sort |> Seq.toList
// ["TV"; "Tv"; "UV"; "Uv"; "tV"; "tv"; "uV"; "uv"]
在另一方面,如果我们使用Linq的F#中,我们得到了相同的结果C#代码:
open System.Linq
strings.OrderBy(fun s -> s).ToArray()
// [|"tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"|]
根据MSDN,所述OrderBy
方法“通过使用默认的比较默认比较键”。
在F#库不使用默认Comparer
,但我们可以用sortWith
:
open System.Collections.Generic
let comparer = Comparer<string>.Default
现在,当我们做这样,我们得到了相同的结果LINQ OrderBy
:
strings |> List.sortWith (fun x y -> comparer.Compare(x,y))
// ["tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"]
或者,我们可以使用内置的CompareTo
功能,可以得到相同的结果:
strings |> List.sortWith (fun x y -> x.CompareTo(y))
// ["tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"]
道德的故事:如果你关心排序,始终指定具体的比较中使用!
由于在了解这个问题上进一步@Richard和his answers指着我的方向开始有点
我的问题似乎已根植在没有完全了解F#中comparison
约束的后果。这里是Seq.sortBy
的签名
Seq.sortBy : ('T -> 'Key) -> seq<'T> -> seq<'T> (requires comparison)
我的假设是,如果类型'T
实现IComparable
那么这将在排序中使用。我应该咨询这个问题,第一:F# comparison vs C# IComparable,其中包含了一些有益的参考,但需要进一步的仔细阅读完全理解到底是怎么回事。
所以,试图回答我自己的问题:
是否有在订货逻辑差异的根本原因?
是。 C#的版本似乎使用的字符串的实现IComparable
的,而F#版本没有。
什么是我的情况,以解决这个“问题”是推荐的方式?
虽然我不能在这是否是“推荐”发表评论,如果有一个对相关类型的F#功能order
下面将使用IComparable
的实现:
let strings = [| "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" |]
let order<'a when 'a : comparison> (sequence: seq<'a>) =
sequence
|> Seq.toArray
|> Array.sortWith (fun t1 t2 ->
match box t1 with
| :? System.IComparable as c1 -> c1.CompareTo(t2)
| _ ->
match box t2 with
| :? System.IComparable as c2 -> c2.CompareTo(t1)
| _ -> compare t1 t2)
let orderedValues = strings |> order
这种现象特定字符串,或是否适用于其他.NET类型呢?
显然有参与了comparison
约束和IComparable
接口之间的关系有些微妙。为了安全起见,我将关注@理查德的意见,并始终明确的那种比较的 - 可能使用上面的函数为“优先”在分拣使用IComparable
。