Lecture 5 summary ================= - We typechecked a complicated match expression and talked about typechecking in general - We desugared let expressions and introduced let with patterns - We talked about higher order functions and currying Other references: - http://www.cs.cornell.edu/Courses/cs3110/2014fa/lectures/5/lec05.html Typechecking example ==================== Question: what is the type of f, defined as follows? let f = fun x -> match x with | a :: [b :: c] :: d -> d | _ -> failwith "Nope!" Answer: to figure this out, we will go through a process similar to what the typechecker does. To start, we know that this expression is a function, so it must have a function type: _________ -> ___________ The first blank is the type of the argument, and the second is the type of the return value (or more precisely, the body). to figure out the argument type, we have to see how the argument is used. Here, x is used in a pattern match, so it must be compatible with the patterns it is matched against. Thus x must be some kind of list, because the pattern it is matched against is a list (_ :: _ :: _ must be a list). We don't know what kind of list it is, so let's for now call it an 'a list. What do we know about 'a? Well, if (a :: [b :: c] :: d) has type 'a list, then a and [b :: c] must both have type 'a, and d must have type 'a list. In order for [b :: c] to have type 'a, 'a must itself be some kind of list. We don't know what kind, so we'll call it a 'b list. So we have 'a = 'b list Similarly, b :: c is itself one of the elements of [b :: c], and b :: c is a list, so the elements of [b :: c] must be lists. We don't know what kind, so we'll call them 'c lists. All we know about 'c is that b : 'c and c : 'c list. Since we never use b and c, we can't figure out anything else about 'c. Let's bring this together. We have that x matches a pattern a :: [b :: c] :: d, so x must have type 'a list. [b :: c] has type 'a, so 'a must be 'b list for some 'b. b :: c is a 'b, so 'b must be 'c list for some 'c. Thus we have x : 'c list list list For some 'c. We also know that x must be matchable against the second pattern. But that doesn't tell us anything about the type of x, because a variable can match any type of value. What about the return type? Well, the type of a match is the type of all the right hand sides of the arrows (which must all be the same). What can we figure out about those? In the first branch, the expression evaluates to d. While looking at the pattern, we decided d must have the same type as x, namely 'a list or 'c list list list. The body of the second branch is (failwith "Nope!"). failwith is a function that simply throws an exception. It has type (string -> 'a), which means failwith "Nope" can be given any type you like. Therefore, this branch tells us nothing about the type of the return value of the whole function. So we're left with fun x -> match x with | a :: [b::c] :: d -> d | _ -> failwith "Nope!" : 'c list list list -> 'c list list list We can always replace a type variable with a different type variable, as long as we do it consistently, and OCaml always uses type variables as close to 'a as possible, so OCaml would tell us val f : 'a list list list -> 'a list list list = Pattern matching let (destructuring let) ======================================== We've already seen that let f x = e1 in e2 is just shorthand for let f = fun x -> e1 in e2 The let expression has even more sugar than that. Note that the following expressions are semantically equivalent: let x = e1 in e2 match e1 with x -> e2 Both of them evaluate e1, then substitute e1 for x in e2 The let expression is better style (because it's more succinct and clearer what's going on), but the match expression is more flexible, because we can include more branches, and because we can write complex patterns to get at a particular piece of a complex structure. In fact, let expressions can also do pattern matching (although they cannot have multiple branches). Whenever an expression defines a new variable (such as let, or fun), you can actually put any pattern. This is especially useful for tuples. For example, if get_point was a function that returned a tuple, you could write get_point : int -> int * int let (x,y) = get_point 5 in x + y This matches the pair returned by get_point with the pattern (x, y); which causes x to be bound to the first element of the pair, and y to the second. This feature makes it look like get_point is returning two values; in fact it is only returning one, but that one value is a tuple. This can be used with functions: fun (x,y) -> x + y is a function that takes a single tuple argument, matches it against the pattern (x,y), and then binds x and y to the first and second components of the tuple. It has type (int * int) -> int And can be called on a pair: let f = (fun (x,y) -> x + y) in f (3,5) Even though OCaml functions only take a single value and only return a single value, we have used a little bit of syntactic sugar to make it seem like functions can take multiple arguments and return multiple values. Higher order functions ====================== OCaml actually has an even cooler way to make functions that seem to take two arguments. Consider the function let f = fun x -> fun y -> x + y This is a function, which when given an argument x, produces a new function. f 3 --> (fun y -> 3 + y) We can then give this function another argument: (f 3) 5 --> (fun y -> 3 + y) 5 --> 3 + 5 --> 8 In fact, OCaml will add parentheses this way if we leave them out, so we have f 3 5 -->* 8 If we use the syntactic sugar we've seen, we have let f = fun x -> fun y -> x + y == is the same as == let f x = fun y -> x + y == is the same as == let f x y = x + y Which we can read as "when you see f x y, replace it by x + y" In this way, we can think of f as a function that takes two int arguments and produces an int, or we can think of it as a function that takes one int, and produces a function that takes a second int and produces an int. This duality of thinking shows up in the type as well. Thinking the former way we would write f : int -> int -> int "f is a function that takes an int and an int and gives an int" but this is just shorthand for f : int -> (int -> int) "f is a function that takes an int and returns a value of type int -> int" Currying and uncurrying ======================= We've shown two ways to write functions that take two arguments. A function can take a tuple: let f (x,y) = x + y val f : int * int -> int = or it can return a function let f x y = x + y val f : int -> int -> int = The latter style is called a curried function; the former is called uncurried (curried functions are spicier). As an exercise, we wrote a function that turns an uncurried function into a curried function let curry f x y = f (x,y) let uncurried_sum (x,y) = x + y uncurried_sum (3,5);; - : int = 8 let curried_sum = curry uncurried_sum curried_sum 3 5;; - : int = 8 The thing that we can do with a curried function that we can't do with an uncurried function is "partially apply it": curried_sum 3;; - : int -> int = This will become more useful in the coming lecture when we talk about programming with higher order functions. Notes ===== Note 1: we've seen many syntaxes (syntaces?) for writing function definitions. The preferred style is usually the curried and sugared let: let f x y z = body Note 2: writing uncurry is a good exercise. Note 3: although I didn't explicitly say this in lecture, a higher order function is a function that either takes another function as an argument or returns one as a value.