2010-11-13 3 views
15

F # дает мне некоторые проблемы с его правилами вывода типа. Я пишу простой построитель вычислений, но не могу получить ограничения на общий тип переменных.Как преобразовать ограничение параметра типа `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 Я не знаю, как бы я перевел ограничение типа 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! 
} 

ответ

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) 

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

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 в функции передается ваш строитель.
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 } 
+1

Хорошо, теперь я уверен, что нет четкого решения для того, что я хотел бы сделать. Указание 'obj' для' finallyAction' имеет неприятный побочный эффект, уменьшающий все мои пользовательские привязанные значения ('let!') В пользовательском потоке операций, чтобы ввести 'obj', что означает, что я не могу действительно разумно работать с ними дольше. Нужно будет подумать о том, как реализовать этот строитель по-разному. Но я надеюсь, что они исправит это в будущей версии языка F # ... – stakx

+1

@stakx: Даже если вы используете 'obj', тип значений, связанных с использованием' let! ', Должен быть фактическим (более конкретным) типом , Я отредактировал ответ, чтобы включить пример, демонстрирующий это (я, наконец, тоже тестировал это :-)). –

+0

Ты волшебник! :) Я предполагаю, что все эти аннотации типа мешали. Благодаря! – stakx

3

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

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

, но позвольте мне его коду чтобы убедиться, что это точно ...

Ах, это выглядит s, как это было бы так:

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 # не делать контрсилами формы «Т1:> Т2», где оба являются переменными типа (это предполагает 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()) 

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

+0

_ "Это Конструкция заставляет код быть менее общим, чем указано аннотациями типа. Тип переменной '' a' была ограничена типом '' z'. "_ :-( – stakx

+0

Спасибо за ваш ответ. Возможно, я смогу решить мою проблему фиксируя некоторую переменную типа для конкретного типа. Я немного разочарован, обнаружив, что F #, у которого есть все это замечательное умозаключение типа, не может сделать что-то, что C# ca n делать легко ... это было неожиданным. – stakx

+0

_ «Но я думаю, что я из других умных идей». Однако я благодарен за ваши усилия. Вы, должно быть, были заинтригованы этим! ;-) Я играл с этим весь день и не нашел приятного решения, поэтому, видя, как другие борются с этим, по крайней мере успокаивает меня, что я не пропустил что-то очень основное. – stakx