首页 文章

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

提问于
浏览
16

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);
        }
    }
}

到目前为止,我为 F# version 提出的最佳(但非编译代码)是:

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.

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

有人可以告诉我正确的F#代码吗?


Background: 我的目标是能够像这样写一个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! 
}

2 回答

  • 3

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

    这似乎与this mailing list中讨论的情况相同 . Don Syme说以下内容:

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

    您始终可以通过在传递给构建器的函数中使用 obj 来解决此问题 .
    EDIT :即使使用 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 }
    
  • 8

    它会是这样的

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

    哦,我明白了,但 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,将在运行时抛出强制转换异常) .

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

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

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

相关问题