咨询F#中两类DoWork方法调用解析存在差异的原因
问题解析:F#中灵活类型约束导致的成员重载解析差异
首先看给出的代码:
type MyClass = class end type MyWorker1() = (*1*)member this.DoWork(myObject: MyClass) = () (*2*)member this.DoWork(myObjects: MyClass seq) = myObjects |> Seq.iter this.DoWork //Resolves to (*1*) type MyWorker2() = (*3*)member this.DoWork(myObject: #MyClass) = () (*4*)member this.DoWork(myObjects: #MyClass seq) = myObjects |> Seq.iter this.DoWork //Resolves to (*4*)
差异原因分析
MyWorker1的行为(具体类型参数)
MyWorker1中两个重载的参数都是具体类型:
- (1)接受
MyClass类型的单个实例 - (2)接受
MyClass seq类型的序列
当在(2)中调用this.DoWork时,Seq.iter需要的是一个MyClass -> unit的函数,(1)的签名完全匹配这个需求,且MyClass和MyClass seq是完全不同的类型,编译器不会产生歧义,直接绑定到(1),不会触发递归调用。
MyWorker2的行为(灵活类型约束)
MyWorker2中两个重载使用了灵活类型约束(#MyClass表示MyClass或其任意子类):
- (3)接受
#MyClass类型的单个实例 - (4)接受
#MyClass seq类型的序列
这里的核心问题出在F#对灵活类型约束的重载解析规则:
- 灵活类型约束
#MyClass本质上是泛型约束的语法糖,相当于'T when 'T :> MyClass。 - 当在(4)中调用
this.DoWork时,编译器需要推断该调用的目标重载。由于当前上下文是处理#MyClass seq,编译器在解析时会优先尝试匹配与上下文类型更“贴合”的重载——也就是接受序列的(4),而非单个元素的(3)。 - 这种错误的匹配会导致递归调用:编译器错误地认为
this.DoWork可以接受序列中的元素(但实际上元素是#MyClass,而(4)需要的是#MyClass seq),最终形成递归绑定。
解决方法
要让MyWorker2的(4)正确调用(3),需要显式指定类型,消除编译器的歧义:
type MyWorker2() = (*3*)member this.DoWork(myObject: #MyClass) = () (*4*)member this.DoWork(myObjects: #MyClass seq) = // 方式1:显式将元素转换为MyClass myObjects |> Seq.iter (fun x -> this.DoWork(x :> MyClass)) // 方式2:显式指定DoWork的函数类型 // myObjects |> Seq.iter (this.DoWork : #MyClass -> unit)
内容的提问来源于stack exchange,提问作者Franco Tiveron
相关产品推荐
相关产品推荐

