如何将 `where T : U` 泛型类型参数约束从 C# 转换为 F#? [英] How do I translate a `where T : U` generic type parameter constraint from C# to F#?
问题描述
F# 的类型推断规则给我带来了一些麻烦.我正在编写一个简单的计算构建器,但无法正确设置通用类型变量约束.
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.
我想要的代码在 C# 中如下所示:
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);
}
}
}
<小时>
到目前为止,我为 F# 版本 想到的最好的(但非编译代码)是:
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.
不幸的是,我不知道如何翻译 where TA : TZ
对 Bind
方法的类型约束.我认为它应该类似于 'a when 'a :>'z
,但 F# 编译器在任何地方都不喜欢这样,我总是最终得到一些约束到另一个的泛型类型变量.
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.
谁能告诉我正确的 F# 代码?
Could someone please show me the correct F# code?
背景:我的目标是能够编写这样的 F# 自定义工作流:
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!
}
推荐答案
我认为在 F# 中不可能写出这样的约束(虽然我不确定为什么).不管怎样,从句法上来说,你会想写这样的东西(正如布赖恩建议的那样):
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:
错误 FS0698:无效的约束:用于约束的类型是密封的,这意味着最多只能通过一种解决方案来满足约束
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
这似乎与 这个邮件列表.唐·赛姆 (Don Syme) 说的内容如下:
This seems to be the same case as the one discussed in this mailing list. Where Don Syme says the following:
这是为了使 F# 类型推断易于处理而施加的限制.特别是,子类型约束右侧的类型必须是名义类型.注意'A :> 形式的约束.'B 总是急切地求解为 'A = 'B,如 F# 规范的第 14.5.2 节(解决子类型约束)中所述.
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.
您始终可以通过在传递给构建器的函数中使用 obj
来解决此问题.
EDIT:即使您使用 obj
,使用 let!
绑定的值也会有更具体的类型(当调用 finallyAction
>, F# 会自动将某个类型参数的值强制转换为 obj
):
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 }
这篇关于如何将 `where T : U` 泛型类型参数约束从 C# 转换为 F#?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!