Lecture 4 summary ================= We discussed lists, and used them as an example of pattern matching with variant types. See also: - http://www.cs.cornell.edu/Courses/cs3110/2014fa/lectures/4/lec04.html Tuples ====== We extended the OCaml syntax with tuples, which bundle multiple values together: e ::= (e1, e2) v ::= (v1, v2) A pair containing a value of type t1 and a value of type t2 has type t1*t2 t ::= t1 * t2 Here are the semantics: (e1, e2) --> (e1', e2) if e1 --> e1' (v1, e2) --> (v1, e2') if e2 --> e2' (e1, e2) : t1 * t2 if e1 : t1 and e2 : t2 (e1, e2){v/x} = (e1{v/x}, e2{v/x}) Tuples can be destructured with pattern matching: let fst = fun x -> match x with | (x, y) -> x let snd = fun x -> match x with | (x, y) -> y fst (1,'a') --> 1 snd (1,'a') --> 'a' Using the syntactic sugar, these can be written as let fst (x,y) = x let snd (x,y) = y These functions are built into OCaml. You can also have tuples and tuple types with more than two components: e ::= (e1, e2, ..., en) v ::= (v1, v2, ..., vn) t ::= t1 * t2 * ... * tn Lists ===== Using variants and tuples we can build lists. Every list of ints is either the empty list, or a list containing a head (which is an int) and a tail (which is itself a list of ints): type int_list = IntNil | IntCons of int * int_list For example, the list 1, 2, 3, 4, 5 could be encoded as IntCons (1, IntCons (2, IntCons (3, IntCons (4, IntCons (5, IntNil))))) We can use pattern matching and recursion to write functions to manipulate IntLists: let rec int_length l = match l with | IntNil -> 0 | IntCons (hd, tl) -> 1 + int_length tl Astute readers will note that I'm annotating everything with int; in order for this code to typecheck, the list can only contain ints. We could repeat this exercise if we wanted to store characters: type char_list = CharNil | CharCons of char * char_list let rec char_length l = match l with | CharNil -> 0 | CharCons (hd, tl) -> 1 + char_length tl char_length (CharCons ('a', CharCons ('b', CharCons ('c', CharNil)))) -->* 3 This is clearly very cumbersome; what we'd like is to create a generic list type and write the length function once. Luckily, OCaml makes this very easy by allowing us to use "type variables": type 'a list = Nil | Cons of 'a * 'a list let rec length l = match l with | Nil -> 0 | Cons (hd, tl) -> 1 + length tl Now we can write let x = Cons(1, Cons(2, Cons(3, Nil))) and let y = Cons('a', Cons('b', Cons('c', Nil))) and check that x : int list and y : char list. Here length : 'a list -> int Although we don't have to repeat ourselves, this is still a bit ugly; because lists are so ubiquitous in OCaml, there is a special syntax for them. Instead of Cons (a,b), OCaml uses the syntax a::b. Instead of Nil, OCaml uses the syntax []. In addition, you can write [e1; e2; e3] in place of e1::e2::e3::[]. Putting this together, we have let rec length l = match l with | _ :: tl -> 1 + length tl | [] -> 0