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.
不幸的是,我不知道如何在where TA : TZ
方法上翻译Bind
类型约束。我认为它应该类似于′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!
}
答案 0 :(得分: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)
不幸的是,这会产生以下错误:
错误FS0698:无效约束:用于约束的类型是密封的,这意味着约束只能通过最多一个解决方案来满足
这似乎与this mailing list中讨论的情况相同。 Don Syme说以下内容:
这是为了使F#类型推断易于处理而施加的限制。特别是,子类型约束右侧的类型必须是名义上的。注意形式'A:&gt;的约束根据F#规范第14.6节的规定,'B总是急切地解决'A ='B。
您始终可以在传递给构建器的函数中使用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 }
答案 1 :(得分: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:&gt; 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
哦,我明白了,但d
和c
现在有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())
但我认为我没有其他聪明的想法。