How do I translate a `where T : U` generic type parameter constraint from C# to F#?

|▌冷眼眸甩不掉的悲伤 提交于 2019-11-29 01:24:59

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!