繁体   English   中英

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

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

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# 版本提出的最好的(但非编译代码)是:

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# 代码吗?


背景:我的目标是能够编写这样的 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# 中编写这样的约束(尽管我不确定为什么)。 无论如何,在语法上,你会想写这样的东西(如布赖恩建议的那样):

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# 类型推断易于处理而施加的限制。 特别是,子类型约束右侧的类型必须是名义类型。 注意 'A :> 'B 形式的约束总是急切地求解为 'A = 'B,如 F# 规范的第 14.5.2 节(解决子类型约束)中所指定。

您始终可以通过在传递给构建器的函数中使用obj来解决此问题。
编辑:即使您使用obj ,使用let!绑定的值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 }

它会像

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM