F#运算符重载谜语

时间:2014-03-12 22:36:31

标签: f# operator-overloading

在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

由于

4 个答案:

答案 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

不确定这里发生了什么,但它不是很一致。