我仍然无法弄清楚为什么 F# 编译器无法推断以下示例中的类型(摘自《Programming F# 3.0》一书):
open System.IO
// type inference ok: fileInfos : string [] -> FileInfo []
let fileInfos (files : string[]) =
Array.map (fun file -> new FileInfo(file)) files
// type inference does not work !?
let fileSizes (fileInfos : FileInfo []) =
Array.map (fun info -> info.Length) fileInfos
书中(第62页)的解释是:
This is because type inference processes code from left-to-right and top-to-bottom, so it sees the lambda passed to Array.map before it sees the type of the array elements passed. (Therefore, the type of the lambda’s parameter is unknown.)
在这种情况下这是合理的(对于fileInfos
,file
的类型被推断为string
,因为构造函数FileInfo
的参数为 string
;对于 fileSizes
,没有此类信息)。
但我仍然心存疑虑,因为如果解释是正确的,那么类型推断(Hindley–Milner 算法 W 的一种变体)就非常有限。确实,还有另一个source说:
... [F#] ... type inference works top-down, bottom-up, front-to-back, back-to-front, middle-out, anywhere there is type information, it will be used.
编辑:感谢大家的回答,我只是在下面添加一些详细信息来解释为什么我仍然感到困惑。
对于fileSizes
,编译器知道:
filesInfo : 文件信息 []
,Array.map : ('a -> 'b) -> 'a [] -> 'b []
,
它可以用 FileInfo
替换 'a
,因此 lambda fun info -> info 中必须有
info : FileInfo
.长度
我可以举一个例子,F# 的类型推断表明它比“从左到右、从上到下”更强大:
// type inference ok: justF : int [] -> (int -> int -> 'a) -> 'a []
let justF (nums : int []) f =
Array.map (fun x -> f x x) nums
其中编译器正确推断出 f : int -> int -> 'a
的类型(显然,如果它只查看 lambda,则无法进行这样的推断)。
最佳答案
F# 的类型推断算法在对象实例成员访问方面受到一定限制 - 它不会尝试从对象实例成员访问中“向后”计算出类型,因此除非已经提供了足够的类型信息,否则它将停止运行。
例如,记录字段的情况并非如此(请注意,即使函数参数上也没有注释):
type FileInfoRecord = { length: int }
let fileSizes fileInfos =
Array.map (fun info -> info.length) fileInfos
然而,这是一个有意识的设计权衡 - 我记得听说它可以改进,但代价是不太可靠的智能感知和更令人困惑的错误消息(即它对于简单的情况可以很好地工作,但是当它失败时,它会在距离导致问题的实际线路更远的地方失败)。使用当前设置,解决方法很简单 - 只需在 lambda 参数上添加注释即可。
我想这就是运行 Hindley-Milner 风格类型推断以及具有多态性和重载的面向对象类型层次结构所付出的代价。
关于f# - F# 类型推断只能自上而下(和左右)工作吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53791467/