2010-11-13 50 views
15

F#給我的類型推理規則帶來一些麻煩。我正在編寫一個簡單的計算生成器,但無法正確獲取我的泛型類型變量約束。如何將`where T:U`通用類型參數約束從C#轉換爲F#?


,我想代碼看起來在C#如下:

class FinallyBuilder<TZ> 
{ 
    readonly Action<TZ> finallyAction; 

    public FinallyBuilder(Action<TZ> finallyAction) 
    { 
     this.finallyAction = finallyAction; 
    } 

    public TB Bind<TA, TB>(TA x, Func<TA, TB> cont) where TA : TZ 
    {          //  ^^^^^^^^^^^^^ 
     try        // this is what gives me a headache 
     {         //  in the F# version 
      return cont(x); 
     } 
     finally 
     { 
      finallyAction(x); 
     } 
    } 
} 

最好的(但非編譯代碼)我已經拿出了F#版本到目前爲止是:

type FinallyBuilder<′z> (finallyAction : ′z -> unit) = 

    member this.Bind (x : ′a) (cont : ′a -> ′b) = 
     try  cont x 
     finally finallyAction (x :> ′z) // cast illegal due to missing constraint 

// Note: ' changed to ′ to avoid bad syntax highlighting here on SO. 

Unfortu最近,我不知道如何翻譯Bind方法的where TA : TZ類型約束。我認爲它應該是類似於′a when ′a :> ′z,但F#編譯器不喜歡這個地方,我總是最終得到一些泛型類型變量約束到另一個。

難道有人請讓我看看正確的F#代碼嗎?


背景:我的目標是能寫的F#的定製工作流程是這樣的:

let cleanup = new FinallyBuilder (fun x -> ...) 

cleanup { 
    let! x = ... // x and y will be passed to the above lambda function at 
    let! y = ... // the end of this block; x and y can have different types! 
} 

回答

8

我不認爲有可能在F#中寫這樣的約束(儘管我不完全確定爲什麼)。無論如何,syntacticalaly,你想寫這樣的事情(如布賴恩建議):

type FinallyBuilder<'T> (finallyAction : 'T -> unit) = 
    member this.Bind<'A, 'B when 'A :> 'T>(x : 'A) (cont : 'A -> 'B) = //' 
    try cont x 
    finally finallyAction (x :> 'T) 

可惜的是,這提供了以下錯誤:

error FS0698: Invalid constraint: the type used for the constraint is sealed, which means the constraint could only be satisfied by at most one solution

這似乎是相同的情況下,作爲一個在this mailing list中討論過。當唐·賽姆說以下內容:

This is a restriction imposed to make F# type inference tractable. In particular, the type on the right of a subtype constraint must be nominal. Note constraints of the form 'A :> 'B are always eagerly solved to 'A = 'B, as specified in section 14.6 of the F# specification.

您可以通過在傳遞給你的建設者功能使用obj總是解決這個問題。
編輯:即使使用obj,使用let!綁定的值將有更具體的類型(主叫finallyAction時,F#會自動施放某種類型的參數來obj值):

type FinallyBuilder(finallyAction : obj -> unit) = 
    member x.Bind(v, f) = 
    try f v 
    finally finallyAction v 
    member x.Return(v) = v 

let cleanup = FinallyBuilder(printfn "%A") 

let res = 
    cleanup { let! a = new System.Random() 
      let! b = "hello" 
      return 3 } 
+1

好吧,我現在相當確信沒有完美的解決方案,我想要做什麼。爲'finallyAction'指定'obj'具有令人討厭的副作用,即將自定義工作流中的所有自定義綁定('let!')值都減少爲鍵入'obj',這意味着我無法真正與他們合作更長的時間。需要更多地考慮如何以不同的方式實現該構建器。但是我希望他們能夠在未來版本的F#語言中解決這個問題...... – stakx 2010-11-13 16:55:25

+1

@stakx:即使你使用'obj',使用'let!'綁定的值的類型應該是實際的(更具體的)類型。我編輯的答案包括一個例子,說明了這一點(我最終也測試了它:-))。 – 2010-11-13 17:27:12

+0

你是一個巫師! :)那麼,我猜所有這些類型的註釋都是如此。謝謝! – stakx 2010-11-13 17:34:53

3

它將會像

...Bind<'A when 'A :> 'Z>... 

但讓我代碼時以確保這是完全正確的...

啊,它看起來就像它會是這樣:

type FinallyBuilder<'z> (finallyAction : 'z -> unit) = 
    member this.Bind<'a, 'b when 'a :> 'z> (x : 'a, cont : 'a -> 'b) : 'b = 
     try  cont x 
     finally finallyAction x //(x :> 'z)// illegal 

除了

http://cs.hubfs.net/forums/thread/10527.aspx

指出F#不做形式「T1:> T2」的約束上,其中兩者都是類型變量(它假定T1 = T2)。然而,對於你的情況,這可能是確定的,你打算用什麼作爲具體實例Z?可能有一個簡單的解決方法或一些不太通用的代碼來滿足這種情況。例如,我在想,如果這個工程:

type FinallyBuilder<'z> (finallyAction : 'z -> unit) = 
    member this.Bind<'b> (x : 'z, cont : 'z -> 'b) : 'b = //' 
     try  cont x 
     finally finallyAction x 

看來:

type FinallyBuilder<'z> (finallyAction : 'z -> unit) = 
    member this.Bind<'b> (x : 'z, cont : 'z -> 'b) : 'b = // ' 
     try  cont x 
     finally finallyAction x 
    member this.Zero() =() 

[<AbstractClass>] 
type Animal() = 
    abstract Speak : unit -> unit 

let cleanup = FinallyBuilder (fun (a:Animal) -> a.Speak()) 

type Dog() = 
    inherit Animal() 
    override this.Speak() = printfn "woof" 

type Cat() = 
    inherit Animal() 
    override this.Speak() = printfn "meow" 

cleanup { 
    let! d = new Dog() 
    let! c = new Cat() 
    printfn "done" 
} 
// prints done meow woof 

哦,我明白了,但dc現在有型Animal。嗯,讓我看看有沒有在我剩餘的聰明......

顯然,你可以做

type FinallyBuilder<'z> (finallyAction : 'z -> unit) = 
    member this.Bind<'a,'b> (x : 'a, cont : 'a -> 'b) : 'b = // ' 
     try  cont x 
     finally finallyAction (x |> box |> unbox) 
    member this.Zero() =() 

它扔掉類型安全(在運行時將拋出一個轉換異常,如果事情是不是最終可行的)。

也可以使特定類型的建設者:

type FinallyBuilderAnimal (finallyAction : Animal -> unit) = 
    member this.Bind<'a,'b when 'a:>Animal>(x : 'a, cont : 'a -> 'b) : 'b = //' 
     try  cont x 
     finally finallyAction x 
    member this.Zero() =() 

let cleanup = FinallyBuilderAnimal (fun a -> a.Speak()) 

但我覺得我出其他的巧妙構思。

+0

_「這構造會導致代碼的泛型不如類型註釋所指示的類型變量''a'被限制爲類型''z'。「_ :-( – stakx 2010-11-13 15:54:07

+0

感謝您的答覆,我或許可以通過以下方式解決我的問題:將一些類型變量修改爲一個具體的類型,我只是有點失望,發現擁有所有這些奇妙類型推理的F#不能做一些C#ca ñ很容易......這是出乎意料的。 – stakx 2010-11-13 16:52:57

+0

_「但我想我已經擺脫了其他聰明的想法。」但是我很感激你的努力。你一定對此感興趣! ;-)我一整天都在玩這個遊戲,並沒有找到一個令人滿意的解決方案,所以看到其他人爲此付出了努力,至少讓我放心,我沒有錯過一些非常基本的東西。 – stakx 2010-11-13 17:27:33

相關問題