首页 > 解决方案 > 通过重载进行 F# 泛化

问题描述

给定类型

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# 的优势之一不就是“简洁和直观”吗?

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

标签: f#overloadinggeneralization

解决方案


这种行为的原因是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)

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


推荐阅读