我正在尝试创建一个实现.NET IComparer接口的类,以便与各种System.Collections.Generic集合一起使用。此IComparer将使用外部提供的函数将集合的值映射到排序键。
示例应用程序可能是SortedSet<City>
,按人口排序,但是从外部数据源动态检索群体,该外部数据源是单独维护和更新的。
open System
open System.Collections.Generic
type ExternalComparer<'T>(compareBy: ('T -> 'U) when 'U :> IComparable) =
let compareBy = compareBy
interface IComparer<'T> with
member this.Compare(a, b) =
let x = compareBy a
let y = compareBy b
if x < y then -1 else if x > y then 1 else 0
编译器在if x < y
表达式上发出以下警告,特别是在x
上:
Warning FS0064 This construct causes code to be less generic than
indicated by the type annotations. The type variable 'U has been
constrained to be type 'IComparable'.
实际上,我打算将类型变量'U约束为类型'IComparable',并且我试图在when 'U :> IComparable
函数的定义中用compareBy
约束来表达该意图类型。
这条警告信息是错误的,还是我做错了什么?
答案 0 :(得分:2)
我认为您的解决方案 - 只接受ne_chunk
类型的函数是正确的,即使这意味着'T -> IComparable
的用户可能需要插入一个upcast来制作代码编译。
为了解释您收到错误的原因,您的类只有一个泛型类型参数ExternalComparer
,并且缺少'T
参数 - 因此编译器将其约束为'U
。如果你想解决这个问题,你必须添加另一个通用参数:
IComparable
此外,我还必须添加type ExternalComparer<'T, 'U when 'U :> IComparable>(compareBy: ('T -> 'U)) =
let compareBy = compareBy
interface IComparer<'T> with
member this.Compare(a, b) =
let x = compareBy a :> IComparable
let y = compareBy b :> IComparable
if x < y then -1 else if x > y then 1 else 0
,以便将结果从:> IComparable
转换为可以比较的'U
。这增加了另一个通用参数,这非常愚蠢。
如果您想避免这种情况,可以使用将IComparable
函数转换为
的静态成员
'T -> 'U
函数在传递给构造函数之前的函数:
'T -> IComparable
现在您可以很好地使用type ExternalComparer<'T>(compareBy: ('T -> IComparable)) =
let compareBy = compareBy
interface IComparer<'T> with
member this.Compare(a, b) =
let x = compareBy a
let y = compareBy b
if x < y then -1 else if x > y then 1 else 0
static member Create(compareBy : 'T -> #IComparable) =
ExternalComparer(fun v -> compareBy v :> IComparable)
方法:
Create
答案 1 :(得分:1)
问题在于,您定义compareBy
类型以返回'U
,约束为IComparable
,此时您可以更明确地将其定义为返回IComparable
直接,像这样:
type ExternalComparer<'T>(compareBy: ('T -> IComparable)) =
由于您知道'U
始终是IComparable
,因此使用泛型类型不正确。
答案 2 :(得分:1)
在我看来,你可以将一大块代码折叠成一个对象表达式:
let bigCity = {Name="NYC"; population = 1_000_000}
let smallCity = {Name="KC"; population = 1000}
let cityComparer compFunc = { new System.Collections.Generic.IComparer<'T> with
override __.Compare(c1, c2) =
let x = compFunc c1
let y = compFunc c2
if x < y then -1 else if x > y then 1 else 0
}
let comp = cityComparer (fun x -> x.population)
comp.Compare(smallCity,bigCity)
//val it : int = -1