从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
'U
类型,因此'U
变为int
(DateTime -> int) -> 'DateTime option -> 'int option
那么为什么F#不能做这个推论呢? Tomas在他的书中提到F#从第一个参数到最后一个参数推断类型,这就是为什么参数的顺序很重要的原因。这就是为什么F#无法推断出第一个例子中的类型。但是为什么F#不能表现得像C#,即尝试从已知的东西开始逐步推断类型?
在大多数情况下,当谈到类型推断时F#更强大......这就是为什么我有点困惑。
答案 0 :(得分:4)
那么为什么F#不能做这个推理呢?
F#可以做到这一点,因为OCaml可以做到这一点。这种更复杂的推理的缺点是错误消息的混淆。 OCaml告诉我们,结果产生了这样难以理解的错误,在实践中,你总是求助于注释类型,以防止编译器被引导到类型推理园路径。因此,没有动力在F#中实现这一点,因为OCaml已经表明它不是很务实。
例如,如果您在OCaml中执行此操作但是拼错了方法名称,那么您将在代码中稍后的某个位置收到一条巨大的错误消息,其中两个推断的类类型不匹配,您将不得不寻找它以查找差异,然后搜索您的代码,以找到错误的实际位置。
IMO,Haskell的类型类也存在同等的实际问题。
答案 1 :(得分:0)
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#类型推断仅限于单个表达式,在此处进行赋值。