7

说,我有

member this.Test (x: 'a) = printfn "generic"
                           1
member this.Test (x: Object) = printfn "non generic"
                               2

如果我用 C# 调用它

var a = test.Test(3);         // calls generic version
var b = test.Test((object)3); // calls non generic version
var c = test.Test<object>(3); // calls generic version

但是,在 F#

let d = test.Test(3);  // calls non generic version
let e = test.Test<int>(3); // calls generic version

所以我必须添加类型注释以获得正确的重载方法。这是真的?如果是这样,那么既然已经推断出参数类型,为什么 F# 不能自动正确解析?(无论如何,F# 的重载决议的顺序是什么?总是Object比它的继承类更受欢迎?)

Object如果一个方法有两个重载,其中一个将参数作为类型,另一个是泛型​​并且都返回相同的类型,这有点危险。(就像在这个例子中,或者Assert.AreEqual在单元测试中),那么我们很可能在没有通知的情况下得到了错误的重载(不会是任何编译器错误)。不会有问题吗?

更新:

有人可以解释

  • 为什么 F# 解析Assert.AreEqual(3, 5)Assert.AreEqual(Object a, Object b)但不是Assert.AreEqual<T>(T a, T b)

  • 但 F# 解析Array.BinarySearch([|2;3|], 2)BinarySearch<T>(T[]array, T value)但不是BinarySearch(Array array, Object value)

4

1 回答 1

9

F# 方法重载解析不如 C# 聪明?

我不认为这是真的。方法重载使类型推断变得更加困难。F# 进行了合理的权衡,以使方法重载可用并且类型推断尽可能强大。

当您将值传递给函数/方法时,F# 编译器会自动将其向上转换为适当的类型。这在许多情况下都很方便,但有时也会令人困惑。

在您的示例中,3向上转换为obj类型。两种方法都适用,但选择了更简单(非通用)的方法。

规范中的第 14.4 节 Method Application Resolution非常清楚地指定了重载规则:

1) 更喜欢其使用不限制用户引入的泛型类型注释的使用等于另一种类型的候选人。

2) 首选不使用 ParamArray 转换的候选人。如果两个候选者都使用类型为 pty1 和 pty2 的 ParamArray 转换,并且 pty1 可能包含 pty2,则更喜欢第二个;也就是说,使用具有更精确类型的候选。

3) 更喜欢没有 ImplicitlyReturnedFormalArgs 的候选人。

4) 更喜欢没有 ImplicitlySuppliedFormalArgs 的候选人。

5) 如果两个候选者有未命名的实际参数类型 ty11...ty1n 和 ty21...ty2n,并且每个 ty1i 要么

一个。可能包含 ty2i,或

湾。ty2i 是 System.Func 类型, ty1i 是其他一些委托类型,然后更喜欢第二个候选者。也就是说,更喜欢任何具有更具体的实际参数类型的候选者,并认为任何 System.Func 类型都比任何其他委托类型更具体。

6)更喜欢不是扩展成员的候选人而不是候选人。

7) 要在两个扩展成员之间进行选择,请选择最近使用 open 产生的那个。

8) 更喜欢非通用的候选而不是通用的候选——也就是说,更喜欢具有空 ActualArgTypes 的候选。

我认为创建明确的重载方法是用户的责任。您始终可以查看推断类型以查看您是否正确执行它们。例如,您的修改版本没有歧义:

type T() =
    member this.Test (x: 'a) = printfn "generic"; 1
    member this.Test (x: System.ValueType) = printfn "non-generic"; 2

let t = T()
let d = t.Test(3)  // calls non-generic version
let e = t.Test(test) // call generic version

更新:

它归结为一个核心概念,协方差。F# 不支持数组、列表、函数等的协变。确保类型安全通常是一件好事(参见此示例)。

所以很容易解释为什么Array.BinarySearch([|2;3|], 2)解析为BinarySearch<T>(T[] array, T value). 这是关于函数参数的另一个示例,其中

T.Test((fun () -> 2), 2)

解决为

T.Test(f: unit -> 'a, v: 'a)

但不是

T.Test(f: unit -> obj, v: obj)
于 2013-01-31T10:41:16.433 回答