How is lazy evaluation working in this example?

Viewed 91

If I have this function: let x = print_endline "foo" in x,x;; it has this signature: unit * unit = ((), ()) and evaluates to foo

I dont understand why it prints foo once.

On the other hand: let x () = print_endline "foo" in x(),x();; will print foo twice and has the signature: unit * unit = ((), ())

can someone explain the principle behind this and how it works in this specific example?

2 Answers

OCaml is an eager language unless directed otherwise. To create lazy values we use the lazy keyword, e.g., lazy (print_endline "foo") will create a lazy value of type unit Lazy.t that will print foo, once it is forced with Lazy.force.

All other values in OCaml are eager and are evaluated immediately. You got confused with the let .. in ... syntactic form.

The syntactic form

let <var> = <expr1> in <expr2>

first evaluates <expr1> and then evaluates <expr2> with the value of <expr1> bound to <var>. Finally, the result of <expr2> becomes the value of the whole form.

So when you say, let x = 2 in x + x you first evaluate 2 and bind it to x and then evaluate x + x with x=2, which is 4, so the value of the expression let x = 2 in x + x in 4. This is a pure expression, it doesn't define any functions and doesn't bind x in the global context.

Going back to your example,

let x = print_endline "foo" in x, x

we first evaluate <expr1>, which is print_endline "foo" that has the side effect of printing foo and returns the value () of type unit. Next, () is bound to x and the <expr2> is evaluated, which x,x, and which is equal to (),().

Your second example,

let x () = print_endline "foo" in x (), x ()

binds x to the function fun () -> print_endline "foo" since

let f x = <expr>

is the syntactic sugar for

let f = fun x -> <expr>

i.e., to a definition of a function.

Therefore, your expression first evaluates <expr1>, which is now fun () -> print_endline "foo" and binds it to x. Now you evaluate x (), x () and calls x () twice, both calls return ().

Finally, you said in your question that,

If I have this function: let x = print_endline "foo" in x,x;;

You don't have any functions there. Much like in let x = 2 in x + x, you are not defining any functions but just evaluate x+x with x bound to 2. After it is evaluated, there are no leftovers, x is not bound to anything. In C-like syntax, it is the same as

{
   int x = 2;
   x + x;
}

So if you want to define a function, you have to use the syntax

let <name> <params> = 
   <expr>

Where <expr> is the body of the function that is evaluated in a context where each parameter is substituted with its actual value (argument).

E.g.,

let print_foo () = 
  print_endline "foo"

or,

let sum_twice x y = 
  let z = x + y in
  2 * z

I would suggest reading the OCaml Tutorial to get a better idea of OCaml syntax.

There is no lazy evaluation involved in this code at all. It would be more the other way around: lazy code (in OCaml) often involves having a function of the form () -> 'a instead of a value 'a, but this doesn't mean that any function of this form has to do with lazy evaluation.

In the first snippet, let x = print_endline "foo" in x, x, you are binding x to a value, the result of print_endline "foo", and then returning x, x. However, by doing so you must call print_endline "foo", which will print foo. At this point, x : unit = (). Then, you return x, x, that is, (), ().

In the second snippet, let x () = print_endline "foo" in x (), x (), you are defining a function x. That form is syntax sugar for let x = fun () -> print_endline "foo" in x (), x (). For this reason, before the evaluation of x (), x (), you have x : unit -> unit = <fun>. Then, you call that function twice, each time printing foo, and returning ().

Note that, when you write something like let x () = ..., you could also be writing let x (y: unit) = ..., except that since the type unit has a single constructor (()), OCaml allows you to deconstruct it directly in the function declaration. Which is why you are allowed to write let x () = ....

Related