2014-03-12 87 views
2

在F#中,操作符重載似乎很強大,但也很難得到正確的結果。 我有以下類: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

感謝

回答

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";} 
+0

其實我會重新解釋這個問題,因爲我問錯了。感謝回答 – Liviu

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 
+0

史詩。該代碼在VS2015中鎖定了IntelliSense很長一段時間。我只需添加一些這些值,然後編輯定義了'v'的行,就可以解決這個問題大約兩分鐘。 – Vandroiy

+0

@Vandroiy我並不感到驚訝,很可能與此[已知錯誤](https://github.com/Microsoft/visualfsharp/issues/343)相關 – Gustavo

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) 

這意味着你不能創建類型的實例10,但可能會滿足您的需求。

+0

奇怪的是,使用記錄類型,即使沒有註釋也能正常工作 - 並允許使用類似'obj'的實例化。看起來像一些不一致的編譯器行爲。 – Vandroiy

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 

不知道這裏發生了什麼,但它不是很一致。