在F#中,运算符重载似乎很强大,但也很难搞定。 我有以下课程:
type Value<'T> =
with
static member inline (+) (a : Value<'U>, b: Value<'U>) : Value<'U> =
do stuff
如果我用+定义另一个重载:
static member inline (+) (a : Value<'U>, b: 'U) : Value<'U> =
do stuff
有效。但如果我想要一个对称算子:
static member inline (+) (b: 'U, a : Value<'U>) : Value<'U> =
do stuff
编译器抱怨:
let a = Value<int>(2);
let b = a + 3 // ok
let c = 3 + a //<-- error here
错误3类型推断问题太复杂(达到最大迭代深度)。请考虑添加其他类型注释
有没有解决方法并保持通用?
我正在使用F#3.1
由于
答案 0 :(得分:3)
编译器永远不会有选择:当您应用(+)
运算符时,您可以给它类型为int
的类型或Value<'U>
类型的东西。 int
类型的某些内容不能被视为类型Value<'U>
,反之亦然。
让我们在翻译中尝试一下。我将两个实现输出A和B,以便我们可以分辨出哪个被调用:
> type Value<'T> =
- { a : 'T }
- with
- static member inline (+) (a : Value<'U>, b: Value<'U>) : Value<'U> =
- printfn "A"; a
- static member inline (+) (a : Value<'U>, b: int) : Value<'U> =
- printfn "B"; a
- ;;
type Value<'T> =
{a: 'T;}
with
static member ( + ) : a:Value<'U> * b:Value<'U> -> Value<'U>
static member ( + ) : a:Value<'U> * b:int -> Value<'U>
end
现在我们有了一个类型。让我们为它做一个值。
> let t = { a = "foo" };;
val t : Value<string> = {a = "foo";}
我们现在可以尝试重载。首先int
:
> t + 4;;
B
val it : Value<string> = {a = "foo";}
确定。现在Value<string>
:
> t + t;;
A
val it : Value<string> = {a = "foo";}
答案 1 :(得分:0)
如another answer中所述,我首选的解决方案是使用基类进行一些重载:
type BaseValue<'T>(v : 'T) =
member x.V = v
type Value<'T>(v : 'T) =
inherit BaseValue<'T>(v : 'T)
static member inline (+) (a : Value<_>, b: Value<_>) = Value(b.V+a.V)
type BaseValue with
static member inline (+) (a: BaseValue<_>, b) = Value(b+a.V)
static member inline (+) (b, a: BaseValue<_>) = Value(b+a.V)
// test
let v = Value(2)
let a = v + v
let b = v + 3
let c = 3 + v
let d = Value(Value 7) + Value(Value 10)
let e = 5 + 7
答案 2 :(得分:0)
请注意,如果您在成员上使用与类本身相同的类型参数,则此问题将消失:
type Value<'t when 't : (static member (+) : 't * 't -> 't)> = V of 't with
static member inline (+)(V(x:'t), V(y:'t)) : Value<'t> = V(x+y)
static member inline (+)(V(x:'t), y:'t) : Value<'t> = V(x+y)
static member inline (+)(x:'t, V(y:'t)) : Value<'t> = V(x+y)
这意味着您无法再创建Value<obj>
类型的实例,但可能会满足您的需求。
答案 3 :(得分:0)
这个运算符定义不合理,但有趣的是,编译器的反应也不合适。
首先,考虑添加两个Value<_>
对象的预期返回类型。它可能是Value<Value<_>>
!除了语言语义之外,没有理由认为像这样的完全通用类型不应该嵌套在自身内。 此+
运算符定义不明。类型推断将始终在添加两个Value<_>
实例的重载上失败,因为编译器将无法解决歧义。
但更根本的问题是:这个运营商甚至意味着什么?添加值并可能将它们换成额外的类型?这是两个完全不同的操作;我没有看到将它们组合起来的优点,更不用说隐含地和可能模棱两可的方式了。为两个Value
实例定义添加并在需要时明确构造它们可能会省去很多麻烦。
除此之外,在这种情况下看起来编译器非常不直观。详细了解F#规范的人可能能够判断它是一个错误还是不一致的设计,但是看看类似变体的行为:
type Value<'T> =
{ Raw : 'T }
static member inline (+) (v, w) = { Raw = v.Raw + w.Raw }
static member inline (+) (v, a) = { Raw = v.Raw + a }
static member inline (+) (a, v) = { Raw = a + v.Raw }
let a = { Raw = 2 }
let b = a + 3 // ok
let c = 3 + a // ok
let d = { Raw = obj() } // No problemo
太远了?试试这个:
type Value<'T> =
val Raw : 'T
new (raw) = { Raw = raw }
static member inline (+) (v : Value<_>, w : Value<_>) = Value(v.Raw + w.Raw)
static member inline (+) (v : Value<_>, a) = Value(v.Raw + a)
static member inline (+) (a, v : Value<_>) = Value(a + v.Raw)
let a = Value(2)
let b = a + 3 // OK
let c = 3 + a // OK
let d = Value(obj) // No problemo
不确定这里发生了什么,但它不是很一致。