首页 文章

为什么F#无法像C#那样推断出类型

提问于
浏览
3

the book by Tomas Petricek开始,以下代码不起作用,因为编译器无法推断出 dt 参数的类型:

> Option.map (fun dt -> dt.Year) (Some(DateTime.Now));;
error FS0072: Lookup on object of indeterminate type.

如果我们明确指定类型一切正常:

> Option.map (fun (dt:DateTime) -> dt.Year) (Some(DateTime.Now));;
val it : int option = Some(2008)

或者我们可以使用流水线操作符来“帮助”编译器推断类型:

> Some(DateTime.Now) |> Option.map (fun dt -> dt.Year);;
val it : int option = Some(2008)

问题是为什么F#编译器无法推断出 dt 参数的类型?在这种特殊情况下,很容易推断 dt 的类型 .

它背后的逻辑可以是:

  • Option.map 的签名是 map : ('T -> 'U) -> 'T option -> 'U option

  • 最后一个参数的类型是 DateTime option

  • 所以 map 用法看起来像 map : ('T -> 'U) -> 'DateTime option -> 'U option

  • 然后编译器可以尝试将 DateTime 替换为 'T 来查看它是否正确,所以我们有 (DateTime -> 'U) -> 'DateTime option -> 'U option

  • 那么它可以通过查看lambda函数的主体来推断 'U 类型,所以 'U 变为 int

  • 我们终于有 (DateTime -> int) -> 'DateTime option -> 'int option

那么为什么F#不能做这个推论呢? Tomas在他的书中提到F#从第一个参数到最后一个参数推断类型,这就是为什么参数的顺序很重要的原因 . 这就是为什么F#无法推断出第一个例子中的类型 . 但是为什么F#不能表现得像C#,即尝试从已知的东西开始逐步推断类型?

在大多数情况下,当谈到类型推断时F#更强大......这就是为什么我有点困惑 .

2 回答

  • 3

    那么为什么F#不能做这个推论呢?

    当OCaml这样做时,F#可以做到这一点 . 这种更复杂的推理的缺点是错误消息的混淆 . OCaml告诉我们,结果产生了这样难以理解的错误,在实践中,你总是求助于注释类型,以防止编译器被引导到类型推理园路径 . 因此,在F#中实现这一点的动机很小,因为OCaml已经证明它不是非常实用 .

    例如,如果您在OCaml中执行此操作但是拼错了方法名称,那么您将在代码中的某个稍后点处收到一条巨大的错误消息,其中两个推断的类类型不匹配,您将不得不通过它来查找差异和然后搜索您的代码以查找错误的实际位置 .

    IMO,Haskell的类型类也存在同等的实际问题 .

  • 1

    F#可以做C#的类型推断可以做的所有事情......还有更多 . AFAIK,C#类型推断的范围是根据赋值的右侧自动键入变量 .

    var x = new Dictionary<string, int>();
    

    等效的F#将是:

    let x = Dictionary()
    

    要么

    let x = Dictionary<_,_>()
    

    要么

    let x = Dictionary<string,_>()
    

    要么

    let x = Dictionary<string,int>()
    

    您可以根据需要提供尽可能多的类型信息,但几乎不会声明 x 的类型 . 因此,即使在这种简单的情况下,F#的类型推断显然要强大得多 . Hindley-Milner type inference键入整个程序,统一所涉及的所有表达式 . 据我所知,C#类型推断仅限于单个表达式,在此处进行赋值 .

相关问题