F#方法的重载分辨率不如C#聪明吗?

时间:2013-01-31 00:28:33

标签: f#

说,我有

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)

1 个答案:

答案 0 :(得分:7)

  

F#方法重载决策不如C#聪明吗?

我不认为这是真的。方法重载使类型推断更加困难。 F#有合理的权衡,使方法重载可用,并且类型推断就像它应该的那样强大。

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

在您的示例中,3被提升为obj类型。这两种方法都适用,但选择了更简单的(非泛型)方法。

规范中的

Section 14.4 Method Application Resolution非常明确地指定了重载规则:

  

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

     

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

<强>更新

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

因此,很容易解释为什么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)
相关问题