2 Imperative Programming with OCaml
2.1 OCaml Imperative Programming
Don’t care whether data is shared in memory
Aliasing is irrelevant
Calling a function f with the same argument always produces the same result. For all ‘x‘ and ‘y‘, we have ‘f x = f y‘ when ‘x = y‘
2.2 Refs
There are two built-in mutable data structures in OCaml: ‘refs‘ and ‘arrays‘. ‘’a ref‘ is pointer to a mutable value of type ‘’a‘. There are three basic operations on references: Allocate a reference: ref e creates a new reference cell containing the value of expression e.
OCaml REPL
ref ;; let x = ref 10;; let y = ref "hello";; # - : 'a -> 'a ref = <fun> # val x : int ref = {contents = 10} # val y : string ref = {contents = "hello"}
OCaml REPL
(!) ;; let t = ref 10;; !t ;; # - : 'a ref -> 'a = <fun> # val t : int ref = {contents = 10} # - : int = 10
OCaml REPL
(:=) ;; let t = ref "hello";; !t ;; t := "world";; !t ;; # - : 'a ref -> 'a -> unit = <fun> # val t : string ref = {contents = "hello"} # - : string = "hello" # - : unit = () # - : string = "world"
OCaml REPL
let z = 3;; let x = ref z;; let y = x;; x := 4;; !y;; ;; # val z : int = 3 # val x : int ref = {contents = 3} # val y : int ref = {contents = 3} # - : unit = () # - : int = 4 #
Here, variables y and x are aliases. In ‘let y = x‘, variable ‘x‘ evaluates to a location, and ‘y‘ is bound to the same location. So, changing the contents of that location will cause both ‘!x‘ and ‘!y‘ to change.
Evaluation of References
Evaluate ‘e‘ to a value ‘v‘, allocate a new location loc in memory to hold ‘v‘, store ‘v‘ in ‘contents‘ of memory at loc, return loc (which is itself a value). For example, e1 := e2 evaluates ‘e2‘ to a value ‘v2‘, evaluates e1 to a location loc, stores ‘v2‘ in ‘contents‘ of memory at loc, returns ‘()‘
Type checking Rules:
(ref e) : t ref if e : t
(e1 := e2) : unit if e1 : t ref and e2 : t 2.3 Sequence
In OCaml, the semicolon ; is used to sequence expressions. It means “evaluate the first expression for its side effects, discard its result, then evaluate the second expression and return its result.”
e1; e2OCaml REPL
let print_both (s, t) = print_string s; print_string t; "Printed s and t";; # val print_both : string * string -> string = <fun>
Note: Dafny verification may timeout. Bold{;;} versus ‘;‘. ‘;;‘ ends an expression in the top-level of OCaml. Use it to say: “Give me the value of this expression”. It is not used in the body of a function. It is not needed after each function definition.
Grouping Sequences
OCaml REPL
let x = ref 0 let f () = begin print_string "hello"; x := !x + 1 end;; let x = ref 0 let f () = ( print_string "hello"; x := !x + 1 ) ;; # val x : int ref = {contents = 0} val f : unit -> unit = <fun> # val x : int ref = {contents = 0} val f : unit -> unit = <fun>
1 ; 2 ;;
(* 2 – value of 2nd expression is returned *)
(1 + 2) ; 4 ;;
(* 4 – value of 2nd expression is returned *)
1 + (2 ; 4) ;;
(* 5 – value of 2nd expression is returned to 1 + *)
1 + 2 ; 4 ;;
(* 4 – because + has higher precedence than ; *)2.4 Implement a Counter
OCaml REPL
let counter = ref 0 ;; let next = fun () -> counter := !counter + 1; !counter ;; next ();; next ();; ;; # val counter : int ref = {contents = 0} # val next : unit -> int = <fun> # - : int = 1 # - : int = 2 #
next ();;
: int = 1
next ();;
: int = 2
let _ = count := 0;
next ();;
: int = 1OCaml REPL
let next = let ctr = ref 0 in fun () -> ctr := !ctr + 1; !ctr;; # val next : unit -> int = <fun>

2.5 The Trade-Off Of Side Effects
Order of evaluation now matters
No referential transparency. Calling the same function with the same arguments may produce different results
Aliasing may result in hard-to-understand bugs. If we call a function with refs r1 and r2, it might do strange things if r1 and r2 are aliases
OCaml REPL
let y = ref 1;; let f _ z = z+1;; (* ignores first arg *) let w = f (y:=2) !y;; w;; # val y : int ref = {contents = 1} # val f : 'a -> int -> int = <fun> # val w : int = 2 # - : int = 2
What if they are evaluated right to left? 2
In OCaml, the order of evaluation is unspecified. This means that the language doesn’t take a stand, and different implementations may do different things. On Mac, OCaml bytecode interpreter and x86 native code evaluates right to left. You should strive to make your programs produce the same answer regardless of evaluation order.
Quiz: If evaluation order is left to right, rather than right to left? Will ‘w‘’s value differ?**
let y = ref 1 in
let f z = z := !z+1; !z in
let w = (f y) + (f y) in
wQuiz: If evaluation order is left to right, rather than right to left? , will w’s value differ?**
let y = ref 1 in
let f z = z := !z+1; !z in
let w = (f y) + !y in
wQuiz: Which f is not referentially transparent? (I.e., not the case that f x = f y for all x = y) ?
A. let f z =
let y = ref z in
y := !y + z;
!y
B. let f =
let y = ref 0 in
fun z ->
y := !y + z; !y
C. let f z =
let y = z in
y+z
D. let f z = z+12.6 Structural vs. Physical Equality
‘=‘ compares objects structurally. ‘<>‘ is the negation of structural equality
‘==‘ compares objects physically. ‘!=‘ is the negation of physical equality
([1;2;3] = [1;2;3]) = true
([1;2;3] <> [1;2;3]) = false
([1;2;3] == [1;2;3]) = false
([1;2;3] != [1;2;3]) = trueWe mostly use ‘=‘ and ‘<>‘. E.g., the ‘=‘ operator is used for pattern matching. But ‘=‘ is a problem with cyclic data structures. If a linked list have a cycle, ‘=‘ will not terminate.
Equality of refs
OCaml REPL
ref 1 = ref 1;; (* true *) ref 1 <> ref 2 ;; (* true *) ref 1 != ref 1;; (* true *) let x = ref 1 in x == x ;; (* true *) ;; # - : bool = true # - : bool = true # - : bool = true # - : bool = true #
2.7 Mutable fields
OCaml REPL
(* create a record type student with fields name, id, and grade *) type point={name:string; id:int; mutable grade:char};; (* create a student record *) let s = {name="john"; id=1234; grade='B'};; (* mutate the grade for the student s *) s.grade <- 'A';; s;; ;; # type point = { name : string; id : int; mutable grade : char; } # val s : point = {name = "john"; id = 1234; grade = 'B'} # - : unit = () # - : point = {name = "john"; id = 1234; grade = 'A'} #
2.8 Implementing Refs
OCaml REPL
type 'a ref = { mutable contents: 'a };; let ref x = { contents = x };; let (!) r = r.contents;; let (:=) r newval = r.contents <- newval;; # type 'a ref = { mutable contents : 'a; } # val ref : 'a -> 'a ref = <fun> # val ( ! ) : 'a ref -> 'a = <fun> # val ( := ) : 'a ref -> 'a -> unit = <fun>
2.9 Arrays
OCaml REPL
let v = [|0.; 1.|];; v.(0) <- 5.;; v;; ;; # val v : float array = [|0.; 1.|] # - : unit = () # - : float array = [|5.; 1.|] #
[|e1; ...; en|]It evaluates to an n-element array, whose elements are initialized to v1 … vn, where e1 evaluates to v1, ..., en evaluates to vn Evaluates them right to left
Here is the type checking rule for arrays;
[|e1; …; en|] : t array If for all i, each ei : tRandom access
e1.(e2) It evaluate e2 to integer value v2, evaluate e1 to array value v1, If 0 ≤ v2 < n, where n is the length of array v1, then return element at offset v2 of v1 Else raise Invalid_argument exception
Here is the type checking rule for the array access:
e1.(e2) : t if e1 : t array and e2 : int Array update
e1.(e2) <- e3Here is the type checking rule for the array update:
e1.(e2) <- e3 : unit if e1 : t array and e2 : int and e3 : t2.10 Control structures
Traditional loop structures are useful with imperative features:
while e1 do e2 done
for x=e1 to e2 do e3 done
for x=e1 downto e2 do e3 doneOCaml REPL
for i = 1 to 5 do Printf.printf "%d " i done;; # 1 2 3 4 5 - : unit = ()
2.11 Hashtbl Module
OCaml REPL
let h = Hashtbl.create 1331;; Hashtbl.add h "alice" 100;; Hashtbl.add h "bob" 200;; Hashtbl.iter (Printf.printf "(%s,%d)\n") h;; # val h : ('_weak1, '_weak2) Hashtbl.t = <abstr> # - : unit = () # - : unit = () # (bob,200) (alice,100) - : unit = ()
2.12 List.assoc as Map
OCaml REPL
let d = [("alice", 100); ("bob", 200); ("cathy", 300)];; (* (string * int) list *) List.assoc "frank" d;; # val d : (string * int) list = [("alice", 100); ("bob", 200); ("cathy", 300)] # Exception: Not_found.
2.13 Build a Map Using Functions
OCaml REPL
let empty v = fun _-> 0;; let update m k v = fun s->if k=s then v else m s let m = empty 0;; let m = update m "foo" 100;; let m = update m "bar" 200;; let m = update m "baz" 300;; m "foo";; (* 100 *) m "bar";; (* 200 *) let m = update m "foo" 101;; m "foo";; (* 101 *) # val empty : 'a -> 'b -> int = <fun> # val update : ('a -> 'b) -> 'a -> 'b -> 'a -> 'b = <fun> val m : '_weak1 -> int = <fun> # val m : string -> int = <fun> # val m : string -> int = <fun> # val m : string -> int = <fun> # - : int = 100 # - : int = 200 # val m : string -> int = <fun> # - : int = 101