X-Git-Url: http://lambda.jimpryor.net/git/gitweb.cgi?p=lambda.git;a=blobdiff_plain;f=week6.mdwn;h=ac94b79c36373bf7377477a83206231c8ec97f2c;hp=1839455a6542b7df4b9339222295d6f871b14358;hb=afc4f7a71546e785d23f59b46fd9d7f035748a60;hpb=80ad862c64373ac07b6e33236a47a50e98583d62 diff --git a/week6.mdwn b/week6.mdwn index 1839455a..ac94b79c 100644 --- a/week6.mdwn +++ b/week6.mdwn @@ -44,9 +44,10 @@ Oh well. Booleans in OCAML, and simple pattern matching ---------------------------------------------- -Where we would write `true 1 2` and expect it to evaluate to `1`, in -OCAML boolean types are not functions (equivalently, are functions -that take zero arguments). Choices are made as follows: +Where we would write `true 1 2` in our pure lambda calculus and expect +it to evaluate to `1`, in OCAML boolean types are not functions +(equivalently, are functions that take zero arguments). Selection is +accomplished as follows: # if true then 1 else 2;; - : int = 1 @@ -69,8 +70,8 @@ Compare with # match 3 with 1 -> 1 | 2 -> 4 | 3 -> 9;; - : int = 9 -Unit ----- +Unit and thunks +--------------- All functions in OCAML take exactly one argument. Even this one: @@ -135,7 +136,7 @@ Oh, one more thing: lambda expressions look like this: # (fun x -> x);; - : 'a -> 'a = # (fun x -> x) true;; - - : book = true + - : bool = true (But `(fun x -> x x)` still won't work.) @@ -152,7 +153,7 @@ reverse the order of the arguments: Infinite loop. -Now consider the following differences: +Now consider the following variations in behavior: # let test = omega omega;; [Infinite loop, need to control c out] @@ -170,3 +171,137 @@ We can use functions that take arguments of type unit to control execution. In Scheme parlance, functions on the unit type are called *thunks* (which I've always assumed was a blend of "think" and "chunk"). +Towards Monads +-------------- + +So the integer division operation presupposes that its second argument +(the divisor) is not zero, upon pain of presupposition failure. +Here's what my OCAML interpreter says: + + # 12/0;; + Exception: Division_by_zero. + +So we want to explicitly allow for the possibility that +division will return something other than a number. +We'll use OCAML's option type, which works like this: + + # type 'a option = None | Some of 'a;; + # None;; + - : 'a option = None + # Some 3;; + - : int option = Some 3 + +So if a division is normal, we return some number, but if the divisor is +zero, we return None: + +
+let div (x:int) (y:int) = 
+  match y with 0 -> None |
+               _ -> Some (x / y);;
+
+(*
+val div : int -> int -> int option = fun
+# div 12 3;;
+- : int option = Some 4
+# div 12 0;;
+- : int option = None
+# div (div 12 3) 2;;
+Characters 4-14:
+  div (div 12 3) 2;;
+      ^^^^^^^^^^
+Error: This expression has type int option
+       but an expression was expected of type int
+*)
+
+ +This starts off well: dividing 12 by 3, no problem; dividing 12 by 0, +just the behavior we were hoping for. But we want to be able to use +the output of the safe division function as input for further division +operations. So we have to jack up the types of the inputs: + +
+let div (x:int option) (y:int option) = 
+  match y with None -> None |
+               Some 0 -> None |
+               Some n -> (match x with None -> None |
+                                       Some m -> Some (m / n));;
+
+(*
+val div : int option -> int option -> int option = 
+# div (Some 12) (Some 4);;
+- : int option = Some 3
+# div (Some 12) (Some 0);;
+- : int option = None
+# div (div (Some 12) (Some 0)) (Some 4);;
+- : int option = None
+*)
+
+ +Beautiful, just what we need: now we can try to divide by anything we +want, without fear that we're going to trigger any system errors. + +I prefer to line up the `match` alternatives by using OCAML's +built-in tuple type: + +
+let div (x:int option) (y:int option) = 
+  match (x, y) with (None, _) -> None |
+                    (_, None) -> None |
+                    (_, Some 0) -> None |
+                    (Some m, Some n) -> Some (m / n);;
+
+ +So far so good. But what if we want to combine division with +other arithmetic operations? We need to make those other operations +aware of the possibility that one of their arguments will trigger a +presupposition failure: + +
+let add (x:int option) (y:int option) = 
+  match (x, y) with (None, _) -> None |
+                    (_, None) -> None |
+                    (Some m, Some n) -> Some (m + n);;
+
+(*
+val add : int option -> int option -> int option = 
+# add (Some 12) (Some 4);;
+- : int option = Some 16
+# add (div (Some 12) (Some 0)) (Some 4);;
+- : int option = None
+*)
+
+ +This works, but is somewhat disappointing: the `add` prediction +doesn't trigger any presupposition of its own, so it is a shame that +it needs to be adjusted because someone else might make trouble. + +But we can automate the adjustment. The standard way in OCAML, +Haskell, etc., is to define a `bind` operator (the name `bind` is not +well chosen to resonate with linguists, but what can you do): + +
+let bind (x: int option) (f: int -> (int option)) = 
+  match x with None -> None | Some n -> f n;;
+
+let add (x: int option) (y: int option)  =
+  bind x (fun x -> bind y (fun y -> Some (x + y)));;
+
+let div (x: int option) (y: int option) =
+  bind x (fun x -> bind y (fun y -> if (0 = y) then None else Some (x / y)));;
+
+(*
+#  div (div (Some 12) (Some 2)) (Some 4);;
+- : int option = Some 1
+#  div (div (Some 12) (Some 0)) (Some 4);;
+- : int option = None
+# add (div (Some 12) (Some 0)) (Some 4);;
+- : int option = None
+*)
+
+ +Compare the new definitions of `add` and `div` closely: the definition +for `add` shows what it looks like to equip an ordinary operation to +survive in a presupposition-filled world, and the definition of `div` +shows exactly what extra needs to be added in order to trigger the +no-division-by-zero presupposition. +