f# - 通过重载进行 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# 的优势之一不就是“简洁和直观”吗?
在这里,我们遇到了一个看起来可能会更好的案例。
解决方案
这种行为的原因是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)
使用运算符可以通过自动推断这些约束来节省很多击键,但正如您在问题中看到的那样,它需要在重载中包含一个虚拟参数。
推荐阅读
- java - io.kubernetes 的源代码仓库:client-java-api
- javascript - 如何从父方法创建子类型的新对象
- python - 不能连接 str 和 float
- c# - 你能发现我的标签助手方法中的数学错误吗?
- r - 使用 {{foo}} 将变量名列表传递给函数
- jquery - 如何使用 Datepicker 更新日期?
- php - 如何将日期字符串(j FY)更改为日期(Ymd)
- java - Kubernetes 中 Pod 之间基于 Cookie 的会话持久性
- mysql - 正确连接 2 个具有多个结果的表
- python-3.x - Scrapy JSON 输出 - 值为空 - 尝试 xpath