8

简而言之,我的问题是:

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

2 回答 2

8

F# 不像 C# 那样进行隐式向上转换。如果您请求 a IComparable,那么您请求的是 aIComparable不是 可以向上转换的东西IComparable

您真正想要的是请求一个恰好实现的类型IComparable,但您仍在使用特定类型。

这就是为什么let x (y : 'a when 'a : comparison),看到它y是 type 'a,而'a可以静态向上转换为comparison(如果你想访问 的成员comparison,你必须首先向上转换为comparisonusing :>

另一方面,let x (y : IComparable<int>) = y非常明确地要求 a IComparable<int>但是您正在传递(1,2)一个可以向上转换为的值IComparable。所以如果你通过(1,2) :> IComparable<int>甚至(1,2) :> _,编译器将能够传递该值。您可以包装可比较的, 您会丢失类型信息,返回值将是 aIComparable而不再是a 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# 构造的工具时,元组类型会以这种形式出现。Tuple 类型是在 .NET Framework 4 中引入的。如果您的目标是 .NET Framework 的早期版本,编译器将使用 F# Core 库 2.0 版本中的 System.Tuple 版本。此库中的类型仅用于面向 .NET Framework 2.0、3.0 和 3.5 版本的应用程序。类型转发用于确保 .NET Framework 2.0 和 .NET Framework 4 F# 组件之间的二进制兼容性。

所以看起来,Tuples 恰好是 System.Tuple 的事实实际上只是一个实现细节,在这一点上,缺乏是IComparison有道理的。

于 2013-08-07T03:53:41.193 回答
1

肯定发生了一些奇怪的事情。FWIW,如果您System.Tuple<_, _>显式构建它,它会起作用,因此这可能是一种解决方法:

let x (y : IComparable) = y
let t = (2, 3)

x (Tuple<_,_> t)
于 2013-08-07T06:47:11.023 回答