您可以像这样实施List.map
:
let rec map project = function
| [] -> []
| head :: tail ->
project head :: map project tail ;;
但相反,它实现如下:
let rec map project = function
| [] -> []
| head :: tail ->
let result = project head in
result :: map project tail ;;
他们说这样做是为了确保投影函数按预期顺序调用,以防它有副作用,例如
map print_int [1;2;3] ;;
应该打印123
,但第一个实现会打印321
。但是,当我自己在OCaml和F#中测试它们时,它们会产生完全相同的123
结果。
(请注意,我在OCaml和F#REPL中对此进行测试 - 评论中的Nick表示这可能是我无法重现的原因,但为什么?)
我误解了什么?有人可以详细说明为什么他们应该生成不同的订单以及我如何重现?这与我之前对过去编写的OCaml代码的理解相反,所以这对我来说很惊讶,我想确保不要重复这个错误。当我阅读这两篇文章时,我将其视为与无关的中间绑定完全相同的东西。
我唯一的猜测是使用cons的表达式评估顺序是从右到左,但这看起来很奇怪?
这纯粹是为了更好地理解OCaml如何执行代码的研究,我真的不需要为生产代码创建自己的List.map
。
答案 0 :(得分:11)
关键是OCaml中的函数应用程序的顺序是未指定,而不是它将处于某种特定的不期望顺序。
评估此表达式时:
project head :: map project tail
首先允许OCaml评估project head
,或者首先评估map project tail
。它选择做哪一个是未指定的。 (理论上,对于不同的调用,订单可能会有所不同。)由于您需要指定的订单,因此需要使用带有let
的表单。
OCaml手册的Section 6.7中记录了订单未指定的事实。请参阅功能应用程序部分:
未指定表达式expr,argument1,...,argumentn的顺序。
(评估订单未指定的说法不是您可以测试的。没有特定订单的案例证明该订单总是会被选中。)
答案 1 :(得分:3)
所以当你有map
这样的实现时:
let rec map f = function
| [] -> []
| a::l -> f a :: map f l
f a
次调用中的所有函数应用程序(map
)都不会按照您期望的顺序按顺序进行评估。所以当你尝试这个时:
map print_int [1;2;3]
你得到了输出
321- : unit list = [(); (); ()]
因为当这些功能应用程序没有按特定顺序执行时。
现在,当你像这样实施map
时:
let rec map f = function
| [] -> []
| a::l -> let r = f a in r :: map f l
您强制按照您期望的顺序执行功能应用程序,因为您明确打电话评估let r = f a
。
所以现在当你尝试:
map print_int [1;2;3]
你会得到
123- : unit list = [(); (); ()]
因为您已明确努力按顺序评估函数应用程序。