在OCaml / F#中重新实现具有正确副作用顺序的List.map?

时间:2017-05-10 01:46:15

标签: f# ocaml

根据this previous answer

您可以像这样实施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

2 个答案:

答案 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 = [(); (); ()]

因为您已明确努力按顺序评估函数应用程序。