F#比较与C#IComparable

时间:2013-08-06 22:53:19

标签: f# comparison icomparable

简而言之,我的问题是:

如何在需要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 ;;

2 个答案:

答案 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)