16

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.

残念ながら、メソッドのwhere TA : TZ型制約をどのように変換するかはわかりません。Bind私はそれがのようなものであるべきだと思った′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! 
}
4

2 に答える 2

9

F# でこのような制約を記述することはできないと思います (理由はよくわかりませんが)。とにかく、構文的には、次のようなものを書きたいと思うでしょう(ブライアンが提案するように):

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) 

残念ながら、これにより次のエラーが発生します。

エラー FS0698: 無効な制約: 制約に使用される型が封印されています。これは、制約が最大 1 つのソリューションによってのみ満たされる可能性があることを意味します

これは、このメーリング リストで議論されているケースと同じようです。Don Syme は次のように述べています。

これは、F# 型の推論を扱いやすくするために課せられた制限です。特に、サブタイプ制約の右側のタイプは名義でなければなりません。'A :> 'B という形式の制約は、F# 仕様のセクション 14.5.2 (サブタイプ制約の解決) で指定されているように、常に 'A = 'B に積極的に解決されることに注意してください。

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 }
于 2010-11-13T16:00:52.493 に答える
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

ああ、そうですか、しかし今は type を持っていdます。うーん、私に賢さが残っているかどうか見てみましょう...cAnimal

まあ、明らかにあなたはできる

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() = ()

タイプセーフを破棄します(物事がfinallyActionableでない場合、実行時にキャスト例外をスローします)。

または、タイプ固有のビルダーを作成できます。

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())

しかし、私は他の賢いアイデアから外れていると思います。

于 2010-11-13T15:43:30.647 に答える