F#运算符重载谜题2

3
在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版本。

谢谢

2个回答

5

去除类型注释可以解决你指出的问题,但你没有注意到还有另一个问题:尝试调用第一个重载时,编译器不知道应该调用哪个重载。遗憾的是,重载分辨不出正确的重载。

一种在编译时使所有内容正常工作的巧妙方法是仅声明第一个重载,并使用重新定义(+)运算符的技巧来定义其余重载,使用一个中间类型:

type Value<'T> = Value of 'T with
    static member inline  (+) (Value a, Value b) = Value (a + b)

type Sum = Sum with
    static member inline (?<-) (Sum, a, b) = a + b
    static member inline (?<-) (Sum, Value a, b) = Value (a + b)
    static member inline (?<-) (Sum, b, Value a) = Value (a + b)

let inline (+) a b :'t = (?<-) Sum a b

// 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

更新

我发现另一种解决方法更好,因为它不需要重新定义(+)运算符,诀窍是创建一个基类并将一些重载移动到那里:

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

嗨Gustavo,感谢这个技巧。我认为这是唯一的解决方法。如果我有更多像Value<'T>这样的类型,这个技巧会起作用吗? - Liviu
我不知道的事情:A)你可以定义三元运算符,这是一种技巧。B)即使? <- 是带有记录参数的三元运算符,如果我调用它(? <-)(sum,a,b),它也无法编译... - Liviu
是的,它可能可以与更多类型一起使用,但并不直观。你不能定义除 (?<-) 以外的三元运算符,调用它的方式要么是 (?<-) a b c,要么是 a ? (b) <- c,没有元组调用。 - Gus
@Gustavo - 我认为你不再需要使用运算符了;你可以在静态成员约束的 (^a or ^b or ...) 部分中使用具有超过2种类型的命名方法。这需要显式注释,但似乎要少得多地进行黑客攻击。 - kvb
@kvb 是的,你说得对。从 F# 3.0 开始,我也可以使用带有约束的方法。这两种解决方案对我来说都有点 hacky,理想情况下,重载分辨率应该会解析出最匹配的方法。 - Gus

0

我认为问题在于当第二个重载函数的第二个参数是Value<'T>类型本身时,无法区分第一个和第二个重载函数之间的差异。

以下是导致错误的完整版本:

type Value<'T>(v : 'T) =
    member x.V = v
    with
        static member inline  (+) (a : Value<'U>, b: Value<'U>) : Value<'U> = 
            Value<'U>(b.V+a.V)

        static member inline  (+) (a : Value<'U>, b: 'U) : Value<'U> = 
            Value<'U>(b+a.V)

        static member inline  (+) (b: 'U, a : Value<'U>) : Value<'U> = 
            Value<'U>(b+a.V)


let a = Value<int>(2);
let b = a + 3
let c = 3 + a

我会建议写一个运算符重载,然后在其中进行类型模式匹配?虽然有点丑。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接