说,我有
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)
答案 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)