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

发布于 2024-10-01 18:21:55 字数 1623 浏览 5 评论 0原文

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# is giving me some trouble with its type inference rules. I'm writing a simple computation builder but can't get my generic type variable constraints right.


The code that I would want looks as follows in 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);
        }
    }
}

The best (but non-compiling code) I've come up with for the F# version so far is:

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.

Unfortunately, I have no clue how I would translate the where TA : TZ type constraint on the Bind method. I thought it should be something like ′a when ′a :> ′z, but the F# compiler doesn't like this anywhere and I always end up with some generic type variable constrained to another.

Could someone please show me the correct F# code?


Background: My goal is to be able to write an F# custom workflow like this:

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! 
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

月牙弯弯 2024-10-08 18:21:55

我认为不可能在 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!绑定的值也将具有更具体的类型(当调用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 }

I don't think it is possible to write constraint like this in F# (although I'm not exactly sure why). Anyway, syntactically, 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) 

Unfortunately, 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.5.2 (Solving Subtype Constraints) 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 }
太阳公公是暖光 2024-10-08 18:21:55

它会是这样的

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文