Как перевести ограничение параметра типа `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())
Но я думаю, что я из других умных идей.