0

给定类型

type T = 
    static member OverloadedMethod(p:int) = ()
    static member OverloadedMethod(p:string) = ()

假设我们要创建一个泛型函数,该函数根据参数的类型解析特定的重载。最直观的方法是

//Case 1

let inline call o = T.OverloadedMethod o //error

call 3
call "3"

但是,尽管有内联定义,但这不起作用,编译器会抱怨

错误 FS0041 无法根据此程序点之前的类型信息确定方法“OverloadedMethod”的唯一重载。可能需要类型注释。候选:静态成员 T.OverloadedMethod : p:int -> unit,静态成员 T.OverloadedMethod : p:string -> unit

我们可以实现我们想要的,例如使用“操作员技巧”

//Case 2

type T2 =

    static member ($) (_:T2, p:int) = T.OverloadedMethod(p)
    static member ($) (_:T2, p:string) = T.OverloadedMethod(p)

let inline call2 o = Unchecked.defaultof<T2> $ o

call2 3
call2 "3"

这里的 F# 编译器(显然)做了更多的工作,而不是简单地回退到 .NET 分辨率。

然而,这看起来很难看,并且意味着代码重复。听起来案例1应该是可能的。

什么技术原因证明这种行为是合理的?我的猜测是有一些权衡(可能与 .NET 互操作性),但找不到更多信息。

编辑

我从帖子中提取了这个原因:

“特征调用是 F# 编译器功能,因此必须有两种不同的方法来编写简单调用和特征调用。对两者使用相同的语法并不方便,因为它可能会造成混淆,在简单调用的情况下可能会出现一些用途被意外编译为特征调用”。

让我们换个角度来看这个问题:

查看代码,编译器应该做什么看起来真的很简单:

1) call是一个内联函数,所以推迟编译到使用站点

2) call 3是一个使用站点,这里的参数是 int 类型的。但是 T.OverloadedMethod(int) 存在,所以让我们生成一个对它的调用

3)像以前的情况一样调用“3”,用字符串代替 int

4)调用 3.0错误,因为 T.OverloadedMethod(float) 不存在

我真的很想看到一个代码示例,其中让编译器执行此操作将是一个问题,证明要求开发人员为 trait 调用编写附加代码是合理的。

归根结底,F# 的优势之一不就是“简洁和直观”吗?

在这里,我们遇到了一个看起来可能会更好的案例。

4

2 回答 2

1

这种行为的原因是T.OverloadedMethod o

let inline call o = T.OverloadedMethod o

不是特征调用。这是必须在调用站点解决的相当简单的 .NET 重载,但是由于您的函数类型并不暗示要解决哪个重载,它根本无法编译,因此需要此功能。

如果你想“推迟”重载决议,你需要做一个特征调用,使函数内联是必要的,但还不够:

let inline call (x:'U) : unit =
    let inline call (_: ^T, x: ^I) = ((^T or ^I) : (static member OverloadedMethod: _ -> _) x)
    call (Unchecked.defaultof<T>, x)

使用运算符可以通过自动推断这些约束来节省很多击键,但正如您在问题中看到的那样,它需要在重载中包含一个虚拟参数。

于 2018-08-16T06:36:45.647 回答
1

权衡源于这样一个事实,即这是一个完全消除的编译器技巧。这意味着:

  1. C# 代码看不到(并利用)任何call2函数,它只能看到你的两个$方法重载。
  2. 所有关于的信息call2在运行时都消失了,这意味着你不能通过反射来调用它,例如。
  3. 它不会出现在调用堆栈中。这可能是好事也可能是坏事——例如,在最新版本的 F# 编译器中,他们选择性地内联了某些函数以使async堆栈跟踪更好一些。
  4. F# 已在调用站点中烘焙。如果您的调用代码是程序集 Acall2来自程序集 B,您不能只用新版本的call2;替换程序集 B。您必须针对新程序集重新编译 A 。这可能是一个向后兼容性问题。

一个相当有趣的好处是它可以在特殊情况下带来显着的性能改进:为什么这个 F# 代码这么慢?. 另一方面,我确信在某些情况下它可能会造成主动伤害,或者只是使生成的 IL 代码膨胀。

于 2018-08-16T03:58:19.397 回答