F#型推理是否只适用于自上而下(和左右)的推理?

F#型推理是否只适用于自上而下(和左右)的推理?,f#,type-inference,F#,Type Inference,我仍然无法理解为什么F#编译器无法在以下示例中推断类型(摘自编程F#3.0书): 书中的解释(第62页)是: 这是因为类型推断从左到右处理代码 从上到下,它可以看到lambda在它之前传递到Array.map 查看传递的数组元素的类型。(因此 lambda的参数未知。) 这在这种情况下是合理的(对于fileInfos,file的类型被推断为string,因为构造函数FileInfo的参数为string;对于fileSizes,没有此类信息) 但我仍然有疑问,因为如果解释是正确的,那么类型推断(H

我仍然无法理解为什么F#编译器无法在以下示例中推断类型(摘自
编程F#3.0
书):

书中的解释(第62页)是:

这是因为类型推断从左到右处理代码 从上到下,它可以看到lambda在它之前传递到Array.map 查看传递的数组元素的类型。(因此 lambda的参数未知。)

这在这种情况下是合理的(对于
fileInfos
file
的类型被推断为
string
,因为构造函数
FileInfo
的参数为
string
;对于
fileSizes
,没有此类信息)

但我仍然有疑问,因为如果解释是正确的,那么类型推断(Hindley–Milner算法W的一种变体)就非常有限。事实上,还有另一种说法:

。。。[F#]。。。类型推断从上到下、从下到上、从前到后进行, 从后到前,从中到外,任何有类型信息的地方,它都会 被使用

编辑:感谢大家的回答,我只是在下面添加了一些细节来解释为什么我仍然感到困惑

文件大小
的情况下,编译器知道:

  • FileInfo:FileInfo[]
  • Array.map:('a->'b)->'a[]->'b[]
它可以用
FileInfo
替换
'a
,因此lambda
fun info->info.Length中必须有
info:FileInfo

我可以举一个例子,F#的类型推断表明它比“从左到右,从上到下”更有效:

如果编译器正确推断出
f:int->int->a的类型(显然,如果它只查看lambda,就不能进行这种推断)。

f#的类型推断算法在对象实例成员访问方面有一定的局限性-它不会试图从它们中“向后”计算出类型,因此,除非已经提供了足够的类型信息,否则它将停滞不前

例如,记录字段不是这样(注意,即使在函数参数上也没有注释):

然而,这是一种有意识的设计权衡——我记得听说它可以改进,但代价是智能感知不太可靠,错误消息更容易混淆(即,对于简单的情况,它可以正常工作,但当它失败时,它会离导致问题的实际线路更远)。对于当前的设置,解决方法很简单——只需在lambda参数上添加注释


我想这就是运行Hindley-Milner风格的类型推断以及具有多态性和重载的面向对象类型层次结构所要付出的代价。

为了使用从左到右的类型推断,您可以使用管道操作符
更改
文件大小。因为它已经知道
fileInfos
FileInfo
的数组,所以可以推断
info
必须是
FileInfo

// type inference now is ok
let fileSizes (fileInfos : FileInfo []) =
    fileInfos |> Array.map (fun info -> info.Length) 
另一种方法是不起作用,因为lambda函数在知道
Array.map
的第二个参数是什么之前就出现了。另一方面,管道操作符已经确定管道后面的任何内容都需要接收
FileInfo
数组

对于
fileInfos
,正如您正确指出的,它知道
file
必须是字符串,因为这是
newfileinfo(…)
接受的唯一类型。现在,假设您想使用
info
的一个OO成员,比如
ToUpper()
。这将返回相同的错误,因为可能有任意数量的类型可以有名为
ToUpper
的成员返回
字符串

// same type inference error
let fileInfos (files : string[]) =
    Array.map (fun file -> new FileInfo(file.ToUpper())) files
同样,您可以通过首先使用管道传递文件来修复它:

let fileInfos (files : string[]) =
    files |> Array.map (fun file -> new FileInfo(file.ToUpper())) 

使用记录类型时,F#会查找具有相同名称的成员的类型,并假定该成员是该类型。当存在多种可能性时,这可能无法正常工作。在这种情况下,它将选择声明或打开的最新版本。

非常感谢,但我仍然感到困惑,因为在这两种情况下,编译器已经从类型注释中知道了
fileInfos:FileInfo[]
。非常感谢,您知道我可以在哪里阅读有关对象类型推断的更多信息吗?。我仍然感到困惑,因为
fileSizes(fileInfos:FileInfo[])
中的类型注释应该足以推断lambda中
info
的类型
fun info->info.Length
不,这还不够,因为类型推断仍然会首先看到
.Length
,而且因为它还没有携带有关
info
类型的信息,所以它就放弃了。您可以通过将文件大小管道化到
Array.map
中来提供这些信息,因此从这一点开始,它就位于“左侧”,并且信息的类型是已知的。就理解其背后的机制而言,这很好,但真正的经验法则解决方案是在编译器抱怨时只放置一个注释,
fun(信息:FileInfo)->info.Length
,使用最少的注释并不会让您得到分数。再次感谢,如果我理解正确,编译器不关心
let fileSizes(fileInfos:FileInfo[])=…
(因为如果是,它已经知道
fileInfos
的类型,那么
info:FileInfo
应该是lambda
fun info->info.Length
@TaThanhDinh:yes,它不建立连接,因为虽然它知道
fileInfos
的类型,但它还没有看到它被传递到
Array.map.Length
时,由于
.Length
是一个成员调用,它就在那时放弃了
// type inference now is ok
let fileSizes (fileInfos : FileInfo []) =
    fileInfos |> Array.map (fun info -> info.Length) 
// same type inference error
let fileInfos (files : string[]) =
    Array.map (fun file -> new FileInfo(file.ToUpper())) files
let fileInfos (files : string[]) =
    files |> Array.map (fun file -> new FileInfo(file.ToUpper()))