如何将`where T:U`generics类型参数约束从C#转换为F#?

F#给我带来了类型推理规则的一些麻烦。 我正在编写一个简单的计算构建器,但无法正确获取我的generics类型变量约束。


我想要的代码在C#中如下所示:

class FinallyBuilder { readonly Action finallyAction; public FinallyBuilder(Action finallyAction) { this.finallyAction = finallyAction; } public TB Bind(TA x, Func 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 (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. 

不幸的是,我不知道如何在Bind方法上转换where TA : TZ类型约束。 我认为它应该像′a when ′a :> ′z ,但是F#编译器在任何地方都不喜欢这样,我总是将一些generics类型变量约束到另一个。

有人可以告诉我正确的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! } 

我不认为在F#中写这样的约束是可能的(尽管我不确定为什么)。 无论如何,在语法上,你想要写这样的东西(正如Brian建议的那样):

 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:无效约束:用于约束的类型是密封的,这意味着约束只能通过最多一个解决方案来满足

这似乎与此邮件列表中讨论的情况相同。 Don Syme说以下内容:

这是为了使F#类型推断易于处理而施加的限制。 特别是,子类型约束右侧的类型必须是名义上的。 注意forms’A:>’B的约束总是急切地解决为’A =’B,如F#规范的第14.6节所述。

您始终可以通过在传递给构建器的函数中使用obj来解决此问题。
编辑 :即使你使用obj ,使用let!绑定的值let! 将有更多特定类型(当调用finallyAction ,F#会自动将某些类型参数的值转换为obj ):

 type FinallyBuilder(finallyAction : obj -> unit) = member x.Bind(v, f) = try fv 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 } 

它会是这样的

 ...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”forms的约束,其中两者都是类型变量(它假设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() = () [] 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 

哦,我明白了,但dc现在有类型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() = () 

抛出类型安全性(如果事情不是finallyActionable,将在运行时抛出强制转换exception)。

或者您可以创建特定于类型的构建器:

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

但我认为我没有其他聪明的想法。