When writing a program in OCaml (resp. when writing a compiler for OCaml programs), one is tempted to simplify one’s program (resp. source programs). The goal of these exercises is to reflect on which simplifications are valid and which are not. To this end, we introduce the following notion of equivalence: two expressions are observationally equivalent if evaluating them carries out the same computation. So for example, 2 + 3 and 3 + 2 are observationally equivalent because evaluating them induces the same computation:
# 2 + 3;;
- : int = 5
# 3 + 2;;
- : int = 5
#
Ditto for 1 + (10 + 100) and (1 + 10) + 100.
Each OCaml program is inductively assembled using the syntax constructors enumerated in the BNF of OCaml:
And evaluating each expression is done based on its top constructor:
Does it mean that evaluating an expression always completes? Not necessarily:
evaluating the expression could raise an error, for example:
# (1 / 0) = 0;;
Exception: Division_by_zero.
#
In this case, the expression 1 / 0, which has type int, does not complete, nor does the expression (1 / 0) = 0, which has type bool.
And how do we know that the expression (1 / 0) = 0 has type bool? That’s why:
# fun () -> (1 / 0) = 0;;
- : unit -> bool = <fun>
#
Ditto if the expression contains an assertion that fails:
# let zero_or_one n = let () = assert (0 <= n && n <= 1) in if n = 0 then "zero" else "one";;
val zero_or_one : int -> string = <fun>
# zero_or_one 2 = "zero";;
Exception: Assert_failure ("//toplevel//", 1969, -55).
#
Evaluating zero_or_one 2 does not complete, nor does evaluating zero_or_one 2 = "zero".
evaluating the expression could also diverge, and so never complete; for example:
# let rec diverge () = diverge () in diverge ();;
C-c C-cInterrupted.
#
Evaluating the sub-expression diverge (), in the body of the letrec-expression or in the body of diverge, does not complete.
or if a recursive call is not a tail call, it can make the OCaml runtime stack (i.e., the area of memory dedicated to managing nested function calls) overflow:
# let rec overflow () = not (overflow ()) in overflow ();;
Stack overflow during evaluation (looping recursion?).
#
Evaluating the sub-expression overflow (), in the body of the letrec-expression or in the body of overflow, does not complete, nor does evaluating not (overflow ()).
or again evaluating an expression could emit traces, e.g., of calls and returns:
# twice_v1 3;;
- : int = 6
# traced_twice_v1 3;;
twice_v1 3 ->
visit 3 ->
visit 2 ->
visit 1 ->
visit 0 ->
visit 0 <- 0
visit 1 <- 2
visit 2 <- 4
visit 3 <- 6
twice_v1 3 <- 6
- : int = 6
#
Evaluating twice_v1 3 and traced_twice_v1 3 both complete, but the latter evaluation can be observed to emit a trace of its recursive calls and return.
Expressions whose evaluation always silently completes (i.e., (1) always yields a value, and (2) performs no observable side effect such as tracing) are said to be pure.
To sum up, just like an OCaml expression is a combination of its parts, evaluating an OCaml expression is a combination of evaluating its parts. If evaluating each of the parts performs no observable side effect and yields a value, then this expression is pure. Otherwise, this expression is impure.
In the exercises below, you are asked whether some OCaml expressions are equivalent – let us call them e1 and e2. To answer this question, you should enumerate each of the possibilities, namely:
are both expressions pure?
then if they always evaluate to the same result, then yes they are equivalent
otherwise they are not equivalent (and you should give an example)
is one pure but the other impure?
then they are not equivalent (and you should give an example)
otherwise, if they are both impure, ask the following questions:
if evaluating each of e1 and e2 elicits the same behaviour (which you should argue they do), then e1 and e2 are equivalent; otherwise, they are not equivalent and you should give an example
Do you think that for any OCaml variable b of type bool, the following expressions are observationally equivalent:
Justify your answer.
Do you think that for any OCaml expression e of type bool, the following expressions are observationally equivalent:
Justify your answer.
Do you think that for any OCaml variable b of type bool, the following expressions are observationally equivalent:
Justify your answer.
Do you think that for any OCaml expression e of type bool, the following expressions are observationally equivalent:
Justify your answer.
Do you think that for any OCaml expression e, the following expressions are observationally equivalent:
Justify your answer.
Do you think that for any OCaml expression e, the following expressions are observationally equivalent:
Justify your answer.
Do you think that for any OCaml expression e, the following expressions are observationally equivalent:
Justify your answer.
Do you think that for any OCaml expression e, the following expressions are observationally equivalent:
Justify your answer.
Do you think that for any OCaml expressions e1 and e2 of type int, the following expressions are observationally equivalent:
Justify your answer.
Reminder: for any OCaml expression e, one can desugar not e into if e then false else true.
For any OCaml expressions e1 and e2 of type bool,
Justify your answers. (For example, e3 and e4 may be arbitrarily large.)
Fixed 4 typos, thanks to Yunjeong Lee’s eagle eye [02 May 2019]
Added the rationale, which is a synthesis of answers to the questions raised in the lecturer’s office [07 Mar 2019]
Fixed the typo if ... then ... then ... into the proper if ... then ... else ... in Exercise 10, Items c and d, thanks to Sviatlana Yasiukova’s eagle eye [04 Mar 2019]
Expanded the introductory paragraph with the notion of observational equivalence [23 Feb 2019]
Created [23 Feb 2019]