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

sta*_*ica 16 c# generics f# type-constraints computation-expression

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,我为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.
Run Code Online (Sandbox Code Playgroud)

不幸的是,我不知道如何where TA : TZBind方法上转换类型约束.我认为它应该是这样的?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! 
}
Run Code Online (Sandbox Code Playgroud)

Tom*_*cek 8

我不认为在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) 
Run Code Online (Sandbox Code Playgroud)

不幸的是,这会产生以下错误:

错误FS0698:无效约束:用于约束的类型是密封的,这意味着约束只能通过最多一个解决方案来满足

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

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

您始终可以通过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 }
Run Code Online (Sandbox Code Playgroud)