Re-implementing List.map in OCaml/F# with correct side effect order?

假如想象 提交于 2019-12-06 00:29:09

问题


According to this previous answer

You could implement List.map like this:

let rec map project = function
  | [] -> []
  | head :: tail ->
      project head :: map project tail ;;

but instead, it is implemented like this:

let rec map project = function
  | [] -> []
  | head :: tail ->
      let result = project head in
      result :: map project tail ;;

They say that it is done this way to make sure the projection function is called in the expected order in case it has side effects, e.g.

map print_int [1;2;3] ;;

should print 123, but the first implementation would print 321. However, when I test both of them myself in OCaml and F#, they produce exactly the same 123 result.

(Note that I am testing this in the OCaml and F# REPLs--Nick in the comments suggests this might be the cause of my inability to reproduce, but why?)

What am I misunderstanding? Can someone elaborate why they should produce different orders and how I can reproduce? This runs contrary to my previous understanding of OCaml code I've written in the past so this was surprising to me and I want to make sure not to repeat the mistake. When I read the two, I read it as exactly the same thing with an extraneous intermediary binding.

My only guess is that the order of expression evaluation using cons is right to left, but that seems very odd?


This is being done purely as research to better understand how OCaml executes code, I don't really need to create my own List.map for production code.


回答1:


So when you have an implementation of map like this:

let rec map f = function
  | [] -> []
  | a::l -> f a :: map f l

none of the function applications (f a) within the map calls are guaranteed to be evaluated sequentially in the order you'd expect. So when you try this:

map print_int [1;2;3]

you get the output

321- : unit list = [(); (); ()]

since by the time those function applications weren't executed in a specific order.

Now when you implement the map like this:

let rec map f = function
  | [] -> []
  | a::l -> let r = f a in r :: map f l

you're forcing the function applications to be executed in the order you're expecting because you explicitly make a call to evaluate let r = f a.

So now when you try:

map print_int [1;2;3]

you will get

123- : unit list = [(); (); ()]

because you've explicitly made an effort to evaluate the function applications in order.




回答2:


The point is that the order of function application in OCaml is unspecified, not that it will be in some specific undesired order.

When evaluating this expression:

project head :: map project tail

OCaml is allowed to evaluate project head first or it can evaluate map project tail first. Which one it chooses to do is unspecified. (In theory it would probably be admissible for the order to be different for different calls.) Since you want a specified order, you need to use the form with let.

The fact that the order is unspecified is documented in Section 6.7 of the OCaml manual. See the section Function application:

The order in which the expressions expr, argument1, …, argumentn are evaluated is not specified.

(The claim that the evaluation order is unspecified isn't something you can test. No number of cases of a particular order prove that that order is always going to be chosen.)



来源:https://stackoverflow.com/questions/43882340/re-implementing-list-map-in-ocaml-f-with-correct-side-effect-order

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!