Как перевести ограничение параметра универсального типа `where T: U` из C # в F #? - PullRequest
16 голосов
/ 13 ноября 2010

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

Ответы [ 2 ]

8 голосов
/ 13 ноября 2010

Я не думаю, что можно написать такое ограничение в 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: недопустимое ограничение: тип, используемый для ограничения, закрыт, что означает, что ограничение может быть удовлетворено не более чем одним решением

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

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

Вы всегда можете решить эту проблему, используя obj в функции, переданной вашему строителю.
EDIT : даже когда вы используете 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 }
3 голосов
/ 13 ноября 2010

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

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

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

...