简而言之,我的问题是:
在需要 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 ;;
请您参考如下方法:
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)
,一个值,可以向上转换为 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# 中那样请求接口(interface)。这是因为 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 还指出,
If you are only using tuples from F# and not exposing them to other languages, and if you are not targeting a version of the .NET Framework that preceded version 4, you can ignore this section.
Tuples are compiled into objects of one of several generic types, all named Tuple, that are overloaded on the arity, or number of type parameters. Tuple types appear in this form when you view them from another language, such as C# or Visual Basic, or when you are using a tool that is not aware of F# constructs. The Tuple types were introduced in .NET Framework 4. If you are targeting an earlier version of the .NET Framework, the compiler uses versions of System.Tuple from the 2.0 version of the F# Core Library. The types in this library are used only for applications that target the 2.0, 3.0, and 3.5 versions of the .NET Framework. Type forwarding is used to ensure binary compatibility between .NET Framework 2.0 and .NET Framework 4 F# components.
所以看起来,Tuples 恰好是 System.Tuple 的事实实际上只是一个实现细节,此时缺少
IComparison
有点道理。