我认为F#函数和System.Func之间的转换必须手动完成,但似乎有一种情况,编译器(有时)会为你做。当出错时,错误信息不准确:
module Foo =
let dict = new System.Collections.Generic.Dictionary<string, System.Func<obj,obj>>()
let f (x:obj) = x
do
// Question 1: why does this compile without explicit type conversion?
dict.["foo"] <- fun (x:obj) -> x
// Question 2: given that the above line compiles, why does this fail?
dict.["bar"] <- f
最后一行无法编译,错误是:
This expression was expected to have type
System.Func<obj,obj>
but here has type
'a -> obj
显然,函数f
没有'a > obj
的签名。如果F#3.1编译器对第一个字典赋值感到满意,那么为什么不是第二个呢?
答案 0 :(得分:5)
应该解释这一点的规范部分是8.13.7 Type Directed Conversions at Member Invocations。简而言之,在调用成员时,将应用从F#函数到委托的自动转换。不幸的是,规范有点不清楚;从措辞看来,这种转换可能适用于任何函数表达式,但实际上它似乎只适用于匿名函数表达式。
规格也有点过时了;在F#3.0类型定向转换中,还可以转换为System.Linq.Expressions.Expression<SomeDelegateType>
。
修改强>
在查看过去与F#团队的一些信件时,我想我已经跟踪了转换如何应用于非语法函数表达式。为了完整起见,我将它包括在这里,但这是一个奇怪的角落情况,所以对于大多数目的,您应该考虑规则是只有语法函数将应用类型定向转换。
例外是重载解析可能导致转换函数类型的任意表达式;部分由14.4 Method Application Resolution部分解释,虽然它非常密集但仍然不完全清楚。基本上,当存在多个重载时,参数表达式只是详细说明;当只有一个候选方法时,参数类型会根据无法解释的参数进行断言(注意:根据转换是否适用,这实际上并不重要,但它在经验上确实很重要)。以下是演示此异常的示例:
type T =
static member M(i:int) = "first overload"
static member M(f:System.Func<int,int>) = "second overload"
let f i = i + 1
T.M f |> printfn "%s"
答案 1 :(得分:0)
编辑:这个答案只解释了对'a -> obj
的神秘推广。 @kvb指出在OP示例中用obj
替换int
仍然不起作用,因此促销本身不足以解释观察到的行为。
为了提高灵活性,F#type elaborator可能会在某些条件下将命名函数从f : SomeType -> OtherType
提升为f<'a where 'a :> SomeType> : 'a -> OtherType
。这是为了减少对upcast的需求。 (见spec. 14.4.2。)
问题2:
dict["bar"] <- f (* Why does this fail? *)
由于f
是&#34;命名函数&#34;,因此其类型将在{秒}之后从f : obj -> obj
升级。 14.4.2看似限制较少的f<'a where 'a :> obj> : 'a -> obj
。但是这种类型与System.Func<obj, obj>
不兼容。
问题1:
dict["foo"] <- fun (x:obj) -> x (* Why doesn't this, then? *)
这很好,因为匿名函数没有命名,所以秒。 14.4.2不适用。该类型永远不会从obj -> obj
升级,因此适合。
我们可以在14.4.2之后观察解释器展示行为:
> let f = id : obj -> obj
val f : (obj -> obj) (* Ok, f has type obj -> obj *)
> f
val it : ('a -> obj) = <fun:it@135-31> (* f promoted when used. *)
(解释器不会向obj
输出约束。)