Как перевести ограничение параметра типа `T: U` с С# на F #?

F # дает мне некоторую проблему с его правилами вывода типов. Я пишу простой построитель вычислений, но не могу получить ограничения на основные переменные типа.


Код, который мне нужен, выглядит следующим образом: С#:

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! 
}

Ответы

Ответ 1

Я не думаю, что можно написать ограничение, подобное этому в 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) 

К сожалению, это дает следующую ошибку:

error FS0698: Недопустимое ограничение: тип, используемый для ограничения, запечатан, что означает, что ограничение может удовлетворяться только одним решением

Это похоже на тот же случай, что обсуждался в этот список рассылки. Где Дон Сим говорит следующее:

Это ограничение, налагаемое на то, чтобы сделать вывод типа F # приемлемым. В частности, тип справа от ограничения подтипа должен быть номинальным. Ограничения записи формы "A: > " B всегда с готовностью решаются на "A =" B, как указано в разделе 14.6 спецификации F #.

Вы всегда можете решить эту проблему, используя 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 }

Ответ 2

Это будет что-то вроде

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

О, я вижу, но d и c теперь имеют тип 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())

Но я думаю, что я из других умных идей.