问题描述
TL DR:我正在尝试创建一个函数包装器。被包装的函数没有参数但返回一个值。
我想这样做的原因是为了创建一个类似于“lock”的函数,但用于信号量。
let functionWrapper (param1) (f:(unit -> 'a)) =
printfn "Calling function %s" param1
f()
签名:(string -> (unit -> 'a) -> 'a)
到目前为止,一切都很好
现在让我们用一个参数丰富 functionWrapper
let functionWrapperFoo = functionWrapper "Foo"
签名:((unit -> obj) -> obj)
出于某种原因,'a 在这里变成了 obj..
现在终于想用这个函数再包装一个了。
但是类型推断会将 functionWrapperFoo
的签名更改为 ((unit -> int) -> int)
,从而阻止此后将函数与其他类型一起使用。
let resultFooInt =
functionWrapperFoo (
fun _ ->
(42)
)
// Because I am using a string as a return type Now,this one bellow won't compile
let resultFooString =
functionWrapperFoo (
fun _ ->
"42"
)
我确实找到了解决方法,但我真的不明白这样做的原因。
我看到一些帖子提到了类似的部分应用程序,但在我的情况下,我的包装函数没有参数
Keeping partially applied function generic
完整的原始代码
let functionWrapper (param1) (f:(unit -> 'a)) =
printfn "Calling function %s" param1
f()
let functionWrapperFoo = functionWrapper "Foo"
let resultFooInt =
functionWrapperFoo (
fun _ ->
(42)
)
let resultFooString =
functionWrapperFoo (
fun _ ->
"42"
)
let functionWrapper (param1) (f:(unit -> 'a)) =
printfn "Calling function %s" param1
f()
let functionWrapperFoo() = functionWrapper "Foo"
let resultFooInt =
functionWrapperFoo() (
fun _ ->
(42)
)
let resultFooString =
functionWrapperFoo() (
fun _ ->
"42"
)
解决方法
这是一个称为值限制的问题。在 this article on automatic generalization in F# 中有相当详细的解释,还有 good existing answers on SO。
基本问题是 F# 不允许您定义通用语法值。您的 functionWrapperFoo
被定义为一个值,即使用 let value = <expr>
。在这种情况下,该值不能是泛型的,即使表达式 <expr>
实际上评估为一个函数。
您的解决方法有效,因为它使用 let func arg = <expr>
将语法声明转化为函数声明。在这里,编译器肯定知道它将是一个函数。
正如 Tom 所提到的,更好的解决方法是在您的 functionWrapperFoo
定义中添加一个参数并将其传递给 functionWrapper
- 这样,它将在语法上定义为一个函数,并且自动泛化将使其通用。
如果您在第一个 full code
部分中将 f 定义为一个值,那么 f 将成为完全类型化的,因此不再是通用的。编译器需要推断某种类型。
如果您将其保留为 workaround
部分中的功能。它仍然是一个(通用)函数,这就是它起作用的原因。
您也可以再次指定函数参数,这会使调用函数不那么难看:
let inline functionWrapper (param1) (f:(unit -> 'a)) =
printfn "Calling function %s" param1
f()
let functionWrapperFoo f = functionWrapper "Foo" f
let resultFooInt =
functionWrapperFoo (
fun _ ->
(42)
)
let resultFooString =
functionWrapperFoo (
fun _ ->
"42"
)