2

My old notes on ML say that

let (₁, … , ₙ) = (₁, … , ₙ) in ′

is a syntactic sugar for

(λ ₙ. … (λ ₁. ′)₁ … )ₙ

and that

let (₁, ₂) =  ′ in ″

is equivalent to

let  =  ′ in 
let ₂ = snd  in 
let ₁ = fst  in 
″

where

  • each (with or without a subscript) stands for a variable,
  • each (with or without a sub- or a superscript) stands for a term, and
  • fst and snd deliver the first and second component of a pair, respectively.

I'm wondering whether I got the evaluation order right because I didn't note the original reference. Could anyone ((confirm or reject) and (supply a reference))?

Chris
  • 26,361
  • 5
  • 21
  • 42
  • Related: https://stackoverflow.com/questions/43883010/is-function-application-evaluation-order-deterministic-in-sml – coredump Jan 03 '23 at 08:57

2 Answers2

3

It shouldn't matter whether it's:

let  =  ′ in 
let ₂ = snd  in 
let ₁ = fst  in 
″

Or:

let  =  ′ in  
let ₁ = fst  in
let ₂ = snd  in 
″

Since neither fst nor snd have any side-effects. Side-effects may exist in the evaluation of but that's done before the let binding takes place.

Additionally, as in:

let (₁, ₂) =  ′ in ″

Neither nor is reliant on the value bound to the other to determine its value, so the order in which they're bound is again seemingly irrelevant.

All of that said, there may be an authoritative answer from those with deeper knowledge of the SML standard or the inner workings of OCaml's implementation. I simply am uncertain of how knowing it will provide any practical benefit.

Practical test

As a practical test, running some code where we bind a tuple of multiple expressions with side-effects to observe order of evaluation. In OCaml (5.0.0) the order of evaluation is observed to be right-to-left. We observe tthe same when it comes to evaluating the contents of a list where those expressions have side-effects as well.

# let f () = print_endline "f"; 1 in
let g () = print_endline "g"; 2 in
let h () = print_endline "h"; 3 in
let (a, b, c) = (f (), g (), h ()) in a + b + c;;
h
g
f
- : int = 6
# let f () = print_endline "f"; 1 in
let g () = print_endline "g"; 2 in
let h () = print_endline "h"; 3 in
let (c, b, a) = (h (), g(), f ()) in a + b + c;;
f
g
h
- : int = 6
# let f _ = print_endline "f"; 1 in
let g () = print_endline "g"; 2 in
let h () = print_endline "h"; 3 in
let a () = print_endline "a" in
let b () = print_endline "b" in
let (c, d, e) = (f [a (); b ()], g (), h ()) in
c + d + e;;
h
g
b
a
f
- : int = 6

In SML (SML/NJ v110.99.3) we observe the opposite: left-to-right evaluation of expressions.

- let
=   fun f() = (print "f\n"; 1)
=   fun g() = (print "g\n"; 2)
=   fun h() = (print "h\n"; 3)
=   val (a, b, c) = (f(), g(), h())
= in
=  a + b + c
= end;
f
g
h
val it = 6 : int
- let
=   fun f() = (print "f\n"; 1)
=   fun g() = (print "g\n"; 2)
=   fun h() = (print "h\n"; 3)
=   val (c, b, a) = (h(), g(), f())
= in
=   a + b + c
= end;
h
g
f
val it = 6 : int
- let
=   fun f _ = (print "f\n"; 1)
=   fun g() = (print "g\n"; 2)
=   fun h() = (print "h\n"; 3)
=   fun a() = print "a\n"
=   fun b() = print "b\n"
=   val (c, d, e) = (f [a(), b()], g(), h())
= in
=   c + d + e
= end;
a
b
f
g
h
val it = 6 : int
Chris
  • 26,361
  • 5
  • 21
  • 42
  • Wow! Thx! The same right-to-left tuple-evaluation order in OCaML 4.11.1 and left-to-right tuple-evaluation order in SMLNJ 110.79. Any idea about whether these two evaluation orders (right-to-left for OCaML and left-to-right for SMLNJ) are intended or specified for the two compilers, and whether these evaluation orders are supposed to hold universally for the two compilers? –  Jan 05 '23 at 01:15
  • I've also tested MosML 2.10 (from http://ppa.launchpad.net/kflarsen/mosml/ubuntu/pool/main/m/mosml/mosml_2.10.2-0ubuntu0_amd64.deb) on your examples and got left-to-right tuple-evaluation order, same as for SMLNJ. –  Jan 05 '23 at 01:30
  • @AlbertNash for OCaml, the evaluation order is [intentionally left unspecified](https://caml.inria.fr/pub/docs/oreilly-book/html/book-ora029.html), so that you don't even try to rely on it. In practice, it's been right-to-left for quite some time, but some implementations have been left-to-right. – jthulhu Jan 05 '23 at 11:59
  • @jthulhu The first two examples on the Web page you cited still produce the same result with OCaML 4.11.1: I get “World!Hello” and “argument”. So the sentence “As it happens, today all implementations of Objective CAML evaluate arguments from left to right.” is simply not true now. –  Jan 05 '23 at 18:33
  • @jthulhu Your “the evaluation order is intentionally left unspecified” is, however, supported by “In Objective CAML, the order of evaluation of arguments is not specified.” from the Web page you cited. It is a bit of a stretch to extend the non-specification of the evaluation order of the arguments of a function to tuple construction. –  Jan 05 '23 at 18:36
  • @AlbertNash indeed; as said, some implementations have been left-to-right, but it's not how it's currently implemented. – jthulhu Jan 05 '23 at 19:29
1

Be aware that, in OCaml, due to the (relaxation of the) value restriction, let a = b in c is not equivalent to (fun a -> c)b. A counterexample is

# let id = fun x -> x in id 5, id 'a';;
- : int * char = (5, 'a')
# (fun id -> id 5, id 'a')(fun x -> x)
Error: This expression has type char but an expression was expected of type int
#

This means that they are semantically not the same construction (the let ... = ... in ... is strictly more general that the other).

This happens because, in general, the type system of OCaml doesn't allow types of the form (∀α.α→α) → int * char (because allowing them would make typing undecidable, which is not very practical), which would be the type of fun id -> id 5, id 'a'. Instead, it resorts to having the less general type ∀α.(α→α) → α * α, which doesn't make it typecheck, because you can't unify both α with char and with int.

jthulhu
  • 7,223
  • 2
  • 16
  • 33
  • @AlbertNash I'm not too sure about where you see a typo, but, indeed, even though people usually think that `let a = b in c` is defined as `(λa. c) b`, it is *not* the case in OCaml. Note however that, even though not strictly equal, there two constructions are still very similar: it's just that, sometimes, OCaml is not able to typecheck the lambda-evaluation form, but is able to typecheck the other. Also, in practice, no programming language actually implements the bindings as function evaluation, for performance reasons. – jthulhu Jan 05 '23 at 11:54
  • The difference between `let = in ` and `(fun -> ) ` lies in type checking, not in β-reduction (which is or at least should be independent of types). –  Jan 05 '23 at 18:39
  • 1
    @AlbertNash ah yes, I saw the typo, I corrected it. – jthulhu Jan 05 '23 at 19:32