委托/ Func转换和误导性编译器错误消息

时间:2014-03-24 10:23:11

标签: f#

我认为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编译器对第一个字典赋值感到满意,那么为什么不是第二个呢?

2 个答案:

答案 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输出约束。)