【问题标题】:F# Method overload resolution not as smart as C#?F# 方法重载解析不如 C# 聪明?
【发布时间】:2013-01-31 00:28:33
【问题描述】:

说,我有

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&lt;T&gt;(T a, T b)

  • 但 F# 将 Array.BinarySearch([|2;3|], 2) 解析为 BinarySearch&lt;T&gt;(T[]array, T value) 而不是 BinarySearch(Array array, Object value)

【问题讨论】:

  • 你似乎遗漏了一个问号……某处。
  • @ildjarn 好的,我会添加一些
  • 你试过test.Test&lt;_&gt;(3)吗?

标签: f#


【解决方案1】:

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

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

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

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

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

1) 更喜欢使用不限制使用 用户引入的泛型类型注解等同于另一种类型。

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

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

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

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

一个。可能包含 ty2i,或

b. 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&lt;T&gt;(T[] array, T value)。这是另一个关于函数参数的示例,其中

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

解析为

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

但不是

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

【讨论】:

  • 有相当多的 BCL .Net 方法被模棱两可地重载(它们在 C# 中解析得很好)。我曾认为 F# 分辨率与 C# 相同,以便在处理 .Net 方法时保持一致性。
  • 方法重载使类型推断变得更加困难。考虑到 F# 是一种函数式编程语言,我认为该语言做出了合理(实用)的选择。
  • 能否请您帮忙看看我更新的问题,为什么 F# 分辨率在 Assert.AreEqualArray.BinarySearch 中的偏好不同?
猜你喜欢
  • 1970-01-01
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多