简而言之,我的问题是:
如何在需要IComparable 的C#容器中存储元组(或任何具有'比较'约束的类型)?
这有效:
> let x (y : 'a when 'a : comparison) = y ;;
val x : y:'a -> 'a when 'a : comparison
> x (1,2) ;;
val it : int * int = (1, 2)
我原以为这会起作用:
> let x (y : IComparable<int>) = y ;;
val x : y:IComparable<int> -> IComparable<int>
> x (1,2) ;;
x (1,2) ;;
---^^^
stdin(28,4): error FS0001: The type ''a * 'b' is not compatible with the type 'IComparable<int>'
这也是:
> let x (y : IComparable) = y ;;
val x : y:IComparable -> IComparable
> x (1,2) ;;
x (1,2) ;;
---^^^
stdin(30,4): error FS0001: The type ''a * 'b' is not compatible with the type 'IComparable'
修改
我遵循F#不进行隐式向上转换的论点。但是,即使是明确的:
> (1, 2) :> IComparable ;;
(1, 2) :> IComparable ;;
^^^^^^^^^^^^^^^^^^^^^
stdin(43,1): error FS0193: Type constraint mismatch. The type
int * int
is not compatible with type
IComparable
The type 'int * int' is not compatible with the type 'IComparable'
我认为这是有道理的,因为F#元组的可比性在结构上是在F#类型系统中推断出来的,也许.NET没有额外的信息。
以下评论似乎有一种解决方法是调用
Tuple<_,_> (1,2) ;;
甚至
box (1, 2) :?> IComparable ;;
答案 0 :(得分:7)
F#不会像C#那样进行隐式向上转换。如果您请求IComparable
,那么您要求IComparable
和不 可以将其转发为IComparable
您真正想要的是请求实现IComparable
的类型,但您仍在使用特定类型。
这就是为什么let x (y : 'a when 'a : comparison)
,y
的类型为'a
,而'a
可以静态上传到comparison
(如果您想访问comparison
的成员,您必须先使用comparison
向:>
投降
另一方面,let x (y : IComparable<int>) = y
非常明确地要求IComparable<int>
。 但您传递的。您可以打包可比较的但 您丢失类型信息,返回值为(1,2)
值是 upcast 到IComparable
。因此,如果您传递(1,2) :> IComparable<int>
甚至(1,2) :> _
,编译器将能够传递值IComparable
,不再是int*int
。
let wrapComparable value =
{
new IComparable with
member this.CompareTo other =
match other with
| :? 'a as other -> compare value other
| _ -> raise <| InvalidOperationException()
}
此外,您需要考虑IComparable
基于obj
,因此您可能需要考虑案例,other
属于不同类型。
如果您需要IComparable<'a>
代码变得更简单:
let wrapComparable value =
{
new IComparable<_> with
member this.CompareTo other = compare value other
}
因此,根据经验,您通常希望使用类型约束创建泛型函数,而不是像在C#中那样请求接口。这是因为F#不进行自动向上转换。
关于平等和比较的非常详细的解释可以在http://lorgonblog.wordpress.com/2009/11/08/motivating-f-equality-and-comparison-constraints/和http://blogs.msdn.com/b/dsyme/archive/2009/11/08/equality-and-comparison-constraints-in-f-1-9-7.aspx中找到。 MSDN还指出,
如果您只使用F#中的元组并且不将它们暴露给其他语言,并且如果您没有针对版本4之前的.NET Framework版本,则可以忽略此部分。
元组被编译成几个泛型类型之一的对象,所有类型都被命名为Tuple,它们在arity上重载,或者类型参数的数量。当您使用其他语言(如C#或Visual Basic)查看元组类型时,或者使用不了解F#构造的工具时,元组类型将以此形式显示。在.NET Framework 4中引入了Tuple类型。如果您的目标是早期版本的.NET Framework,则编译器将使用来自F#Core Library 2.0版本的System.Tuple版本。此库中的类型仅用于面向.NET Framework的2.0,3.0和3.5版本的应用程序。类型转发用于确保.NET Framework 2.0和.NET Framework 4 F#组件之间的二进制兼容性。
看起来,事实上,Tuples恰好是System.Tuple,实际上只是一个实现细节,缺少IComparison
有点意义。
答案 1 :(得分:1)
绝对有些奇怪。 FWIW,如果您明确构造System.Tuple<_, _>
,它可以工作,这可能是一种解决方法:
let x (y : IComparable) = y
let t = (2, 3)
x (Tuple<_,_> t)