How do I translate a `where T : U` generic type parameter constraint from C# to F#?
I don't think it is possible to write constraint like this in F# (although I'm not exactly sure why). Anyway, syntacticalaly, you'd want to write something like this (as Brian suggests):
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, this gives the following error:
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 seems to be the same case as the one discussed in this mailing list. Where Don Syme says the following:
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.5.2 (Solving Subtype Constraints) of the F# specification.
You can always solve this by using obj
in the function passed to your builder.
EDIT: Even when you use obj
, the values bound using let!
will have more specific types (when calling finallyAction
, F# will automatically cast the value of some type parameter to 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 }
It will be something like
...Bind<'A when 'A :> 'Z>...
but let me code it up to ensure that's exactly right...
Ah, it looks like it would be this:
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
except that
http://cs.hubfs.net/forums/thread/10527.aspx
points out that F# does not do contraints of the form "T1 :> T2" where both are type variables (it assumes T1 = T2). However this might be ok for your case, what exactly did you plan to use as concrete instantiations of Z
? There is probably a simple workaround or some less-generic code that will meet the scenario. For example, I wonder if this works:
type FinallyBuilder<'z> (finallyAction : 'z -> unit) =
member this.Bind<'b> (x : 'z, cont : 'z -> 'b) : 'b = //'
try cont x
finally finallyAction x
It seems to:
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
Oh, I see, but d
and c
now have type Animal
. Hm, let me see if there is any remaining cleverness in me...
Well, obviously you can do
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() = ()
which throws away type safety (will throw a cast exception at runtime if the thing is not finallyActionable).
Or you can make type-specific builders:
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())
But I think I am out of other clever ideas.