From 2a5b500ba1a4da0ebae07837c82810ab418a8812 Mon Sep 17 00:00:00 2001 From: Jim Pryor Date: Mon, 1 Nov 2010 00:44:57 -0400 Subject: [PATCH] week7: tweaking Signed-off-by: Jim Pryor --- week7.mdwn | 280 ++++++++++++++++++++++++++++--------------------------------- 1 file changed, 128 insertions(+), 152 deletions(-) diff --git a/week7.mdwn b/week7.mdwn index 8b74a1cc..351a6d95 100644 --- a/week7.mdwn +++ b/week7.mdwn @@ -9,10 +9,10 @@ week 6 [[Towards Monads]]. In those notes, we saw a way to separate thinking about error conditions (such as trying to divide by zero) from thinking about normal arithmetic computations. We did this by making use of the -option monad: in each place where we had something of type `int`, we +`option` type: in each place where we had something of type `int`, we put instead something of type `int option`, which is a sum type -consisting either of just an integer, or else some special value which -we could interpret as signaling that something had gone wrong. +consisting either of one choice with an `int` payload, or else a `None` +choice which we interpret as signaling that something has gone wrong. The goal was to make normal computing as convenient as possible: when we're adding or multiplying, we don't have to worry about generating @@ -39,16 +39,15 @@ material that otherwise would trigger a presupposition violation; but, not surprisingly, these refinements will require some more sophisticated techniques than the super-simple option monad.] -So what exactly is a monad? As usual, we're not going to be pedantic -about it, but for our purposes, we can consider a monad to be a system +So what exactly is a monad? We can consider a monad to be a system that provides at least the following three elements: * A complex type that's built around some more basic type. Usually - it will be polymorphic, and so can apply to different basic types. + the complex type will be polymorphic, and so can apply to different basic types. In our division example, the polymorphism of the `'a option` type provides a way of building an option out of any other type of object. - People often use a container metaphor: if `x` has type `int option`, - then `x` is a box that (may) contain an integer. + People often use a container metaphor: if `u` has type `int option`, + then `u` is a box that (may) contain an integer. type 'a option = None | Some of 'a;; @@ -90,14 +89,14 @@ that provides at least the following three elements: let bind u f = match u with None -> None | Some x -> f x;; val bind : 'a option -> ('a -> 'b option) -> 'b option = - Note the type. `bind` takes two arguments: first, a monadic "box" - (in this case, an 'a option); and second, a function from + Note the type: `bind` takes two arguments: first, a monadic box + (in this case, an `'a option`); and second, a function from ordinary objects to monadic boxes. `bind` then returns a monadic - value: in this case, a 'b option (you can start with, e.g., int options - and end with bool options). + value: in this case, a `'b option` (you can start with, e.g., `int option`s + and end with `bool option`s). - Intuitively, the interpretation of what `bind` does is like this: - the first argument is a monadic value u, which + Intuitively, the interpretation of what `bind` does is this: + the first argument is a monadic value `u`, which evaluates to a box that (maybe) contains some ordinary value, call it `x`. Then the second argument uses `x` to compute a new monadic value. Conceptually, then, we have @@ -105,41 +104,39 @@ that provides at least the following three elements: let bind u f = (let x = unbox u in f x);; The guts of the definition of the `bind` operation amount to - specifying how to unbox the monadic value `u`. In the bind - opertor for the option monad, we unboxed the option monad by - matching the monadic value `u` with `Some x`---whenever `u` + specifying how to unbox the monadic value `u`. In the `bind` + opertor for the option monad, we unboxed the monadic value by + matching it with the pattern `Some x`---whenever `u` happened to be a box containing an integer `x`, this allowed us to get our hands on that `x` and feed it to `f`. - If the monadic box didn't contain any ordinary value, then - we just pass through the empty box unaltered. + If the monadic box didn't contain any ordinary value, + we instead pass through the empty box unaltered. In a more complicated case, like our whimsical "singing box" example from before, if the monadic value happened to be a singing box containing an integer `x`, then the `bind` operation would probably be defined so as to make sure that the result of `f x` was also - a singing box. If `f` also inserted a song, you'd have to decide + a singing box. If `f` also wanted to insert a song, you'd have to decide whether both songs would be carried through, or only one of them. There is no single `bind` function that dictates how this must go. For each new monadic type, this has to be worked out in an useful way. -So the "option/maybe monad" consists of the polymorphic option type, the -unit/return function, and the bind function. With the option monad, we can -think of the "safe division" operation +So the "option/maybe monad" consists of the polymorphic `option` type, the +`unit`/return function, and the `bind` function. With the option monad, we can +think of the "safe division" operation: -
-# let divide' num den = if den = 0 then None else Some (num/den);;
-val divide' : int -> int -> int option = 
-
+ # let safe_divide num den = if 0 = den then None else Some (num/den);; + val safe_divide : int -> int -> int option = as basically a function from two integers to an integer, except with this little bit of option plumbing on the side. A note on notation: Haskell uses the infix operator `>>=` to stand for `bind`. Chris really hates that symbol. Following Wadler, he prefers to -use an infix five-pointed star, or on a keyboard, `*`. Jim on the other hand +use an infix five-pointed star ⋆, or on a keyboard, `*`. Jim on the other hand thinks `>>=` is what the literature uses and students won't be able to avoid it. Moreover, although ⋆ is OK (though not a convention that's been picked up), overloading the multiplication symbol invites its own confusion and Jim feels very uneasy about that. If not `>>=` then we should use @@ -175,39 +172,39 @@ As we proceed, we'll be seeing a variety of other monad systems. For example, an # type 'a list -The unit/return operation is: +The `unit`/return operation is: # let unit x = [x];; val unit : 'a -> 'a list = -That is, the simplest way to lift an 'a into an 'a list is just to make a -singleton list of that 'a. Finally, the bind operation is: +That is, the simplest way to lift an `'a` into an `'a list` is just to make a +singleton list of that `'a`. Finally, the `bind` operation is: # let bind u f = List.concat (List.map f u);; val bind : 'a list -> ('a -> 'b list) -> 'b list = -What's going on here? Well, consider (List.map f u) first. This goes through all -the members of the list u. There may be just a single member, if `u = unit a` -for some a. Or on the other hand, there may be no members, or many members. In -any case, we go through them in turn and feed them to f. Anything that gets fed -to f will be an 'a. f takes those values, and for each one, returns a 'b list. +What's going on here? Well, consider `List.map f u` first. This goes through all +the members of the list `u`. There may be just a single member, if `u = unit x` +for some `x`. Or on the other hand, there may be no members, or many members. In +any case, we go through them in turn and feed them to `f`. Anything that gets fed +to `f` will be an `'a`. `f` takes those values, and for each one, returns a `'b list`. For example, it might return a list of all that value's divisors. Then we'll -have a bunch of 'b lists. The surrounding `List.concat ( )` converts that bunch -of 'b lists into a single 'b list: +have a bunch of `'b list`s. The surrounding `List.concat ( )` converts that bunch +of `'b list`s into a single `'b list`: # List.concat [[1]; [1;2]; [1;3]; [1;2;4]] - : int list = [1; 1; 2; 1; 3; 1; 2; 4] So now we've seen two monads: the option/maybe monad, and the list monad. For any monadic system, there has to be a specification of the complex monad type, -which will be parameterized on some simpler type 'a, and the unit/return -operation, and the bind operation. These will be different for different +which will be parameterized on some simpler type `'a`, and the `unit`/return +operation, and the `bind` operation. These will be different for different monadic systems. Many monadic systems will also define special-purpose operations that only make sense for that system. -Although the unit and bind operation are defined differently for different +Although the `unit` and `bind` operation are defined differently for different monadic systems, there are some general rules they always have to follow. @@ -219,30 +216,28 @@ them from hurting the people that use them or themselves. * **Left identity: unit is a left identity for the bind operation.** That is, for all `f:'a -> 'a m`, where `'a m` is a monadic - value, we have `(unit x) * f == f x`. For instance, `unit` is itself + type, we have `(unit x) * f == f x`. For instance, `unit` is itself a function of type `'a -> 'a m`, so we can use it for `f`: -
-# let ( * ) u f = match u with None -> None | Some x -> f x;;
-val ( * ) : 'a option -> ('a -> 'b option) -> 'b option = 
-# let unit x = Some x;;
-val unit : 'a -> 'a option = 
-
-# unit 2;;
-- : int option = Some 2
-# unit 2 * unit;;
-- : int option = Some 2
-
-# divide 6 2;;
-- : int option = Some 3
-# unit 2 * divide 6;;
-- : int option = Some 3
-
-# divide 6 0;;
-- : int option = None
-# unit 0 * divide 6;;
-- : int option = None
-
+ # let ( * ) u f = match u with None -> None | Some x -> f x;; + val ( * ) : 'a option -> ('a -> 'b option) -> 'b option = + # let unit x = Some x;; + val unit : 'a -> 'a option = + + # unit 2;; + - : int option = Some 2 + # unit 2 * unit;; + - : int option = Some 2 + + # divide 6 2;; + - : int option = Some 3 + # unit 2 * divide 6;; + - : int option = Some 3 + + # divide 6 0;; + - : int option = None + # unit 0 * divide 6;; + - : int option = None The parentheses is the magic for telling OCaml that the function to be defined (in this case, the name of the function @@ -254,30 +249,28 @@ is `*`, pronounced "bind") is an infix operator, so we write (u * f) * g == u * (fun x -> f x * g) If you don't understand why the lambda form is necessary (the "fun - x" part), you need to look again at the type of bind. + x" part), you need to look again at the type of `bind`. Some examples of associativity in the option monad: -
-# Some 3 * unit * unit;; 
-- : int option = Some 3
-# Some 3 * (fun x -> unit x * unit);;
-- : int option = Some 3
-
-# Some 3 * divide 6 * divide 2;;
-- : int option = Some 1
-# Some 3 * (fun x -> divide 6 x * divide 2);;
-- : int option = Some 1
-
-# Some 3 * divide 2 * divide 6;;
-- : int option = None
-# Some 3 * (fun x -> divide 2 x * divide 6);;
-- : int option = None
-
+ # Some 3 * unit * unit;; + - : int option = Some 3 + # Some 3 * (fun x -> unit x * unit);; + - : int option = Some 3 + + # Some 3 * divide 6 * divide 2;; + - : int option = Some 1 + # Some 3 * (fun x -> divide 6 x * divide 2);; + - : int option = Some 1 + + # Some 3 * divide 2 * divide 6;; + - : int option = None + # Some 3 * (fun x -> divide 2 x * divide 6);; + - : int option = None Of course, associativity must hold for arbitrary functions of type `'a -> 'a m`, where `m` is the monad type. It's easy to -convince yourself that the bind operation for the option monad +convince yourself that the `bind` operation for the option monad obeys associativity by dividing the inputs into cases: if `u` matches `None`, both computations will result in `None`; if `u` matches `Some x`, and `f x` evalutes to `None`, then both @@ -288,12 +281,10 @@ to `g y`. * **Right identity: unit is a right identity for bind.** That is, `u * unit == u` for all monad objects `u`. For instance, -
-# Some 3 * unit;;
-- : int option = Some 3
-# None * unit;;
-- : 'a option = None
-
+ # Some 3 * unit;; + - : int option = Some 3 + # None * unit;; + - : 'a option = None More details about monads @@ -308,7 +299,9 @@ serving as the left and right identity. That is, temporarily using presented here, a monad is not exactly a monoid, because (unlike the arguments of a monoid operation) the two arguments of the bind are of different types. But it's possible to make the connection between -monads and monoids much closer. This is discussed in [[Advanced Notes/Monads in Category Theory]]. See also . +monads and monoids much closer. This is discussed in [Monads in Category +Theory](/advanced_notes/monads_in_category_theory). +See also . Here are some papers that introduced monads into functional programming: @@ -326,8 +319,8 @@ invited talk, *19'th Symposium on Principles of Programming Languages*, ACM Pres There's a long list of monad tutorials on the [[Offsite Reading]] page. Skimming the titles makes me laugh. -In the presentation we gave above, which is the usual one in functional programming, we took unit/return and bind as the primitive operations. From these a number of other general monad operations can be derived. It's also possible to take some of the others as primitive, and define for example bind in terms of them. This is discussed some in the [[Advanced Notes/Monads in Category Theory]]. - +In the presentation we gave above---which follows the functional programming conventions---we took `unit`/return and `bind` as the primitive operations. From these a number of other general monad operations can be derived. It's also possible to take some of the others as primitive. The [Monads in Category +Theory](/advanced_notes/monads_in_category_theory) notes do so, for example. Here are some of the specifics. You don't have to master these; they're collected here for your reference. @@ -350,7 +343,7 @@ The `lift` operation we asked you to define for last week's homework is a common # let even x = (x mod 2 = 0);; val g : int -> bool = -`even` has the type int -> bool. Now what if we want to convert it into an operation on the option/maybe monad? +`even` has the type `int -> bool`. Now what if we want to convert it into an operation on the option/maybe monad? # let lift g = fun u -> bind u (fun x -> Some (g x));; val lift : ('a -> 'b) -> 'a option -> 'b option = @@ -363,20 +356,19 @@ also define a lift operation for binary functions: `lift (+)` will now be a function from `int option`s and `int option`s to `int option`s. This should look familiar to those who did the homework. -The `lift` operation (just `lift`, not `lift2`) is sometimes also called the `map` operation. (In Haskell, they say `fmap` or `<$>`.) And indeed when we're working with list monad, `lift f` is exactly `List.map f`! +The `lift` operation (just `lift`, not `lift2`) is sometimes also called the `map` operation. (In Haskell, they say `fmap` or `<$>`.) And indeed when we're working with the list monad, `lift f` is exactly `List.map f`! -Wherever we have a well-defined monad, we can define a lift/map operation for that monad. The examples above used `Some (g x)` and so on; in the general case we'd use `unit (g x)`, using the pre-defined `unit` operation for the monad we're working with. +Wherever we have a well-defined monad, we can define a lift/map operation for that monad. The examples above used `Some (g x)` and so on; in the general case we'd use `unit (g x)`, using the specific `unit` operation for the monad we're working with. In general, any lift/map operation can be relied on to satisfy these laws: * lift id = id * lift (compose f g) = compose (lift f) (lift g) -where id is `fun x -> x` and `compose a b` is `fun x -> a (b x)`. If you think about the special case of the map operation on lists, this should make sense. `List.map id lst` should give you back `lst` again. And you'd expect these +where `id` is `fun x -> x` and `compose f g` is `fun x -> f (g x)`. If you think about the special case of the map operation on lists, this should make sense. `List.map id lst` should give you back `lst` again. And you'd expect these two computations to give the same result: List.map (fun x -> f (g x)) lst - List.map f (List.map g lst) Another general monad operation is called `ap` in Haskell---short for "apply." (They also use `<*>`, but who can remember that?) This works like this: @@ -384,12 +376,12 @@ Another general monad operation is called `ap` in Haskell---short for "apply." ( ap [f] [x; y] = [f x; f y] ap (Some f) (Some x) = Some (f x) -and so on. Here are the laws that any ap operation can be relied on to satisfy: +and so on. Here are the laws that any `ap` operation can be relied on to satisfy: - ap (unit id) v = v + ap (unit id) u = u ap (ap (ap (return compose) u) v) w = ap u (ap v w) ap (unit f) (unit x) = unit (f x) - ap u (unit y) = ap (unit (fun f -> f y)) u + ap u (unit x) = ap (unit (fun f -> f x)) u Another general monad operation is called `join`. This is the operation that takes you from an iterated monad to a single monad. Remember when we were explaining the `bind` operation for the list monad, there was a step where we went from: @@ -520,14 +512,12 @@ Characters (characters in the computational sense, i.e., letters like `'a'` and `'b'`, not Kaplanian characters) will model individuals, and OCaml booleans will serve for truth values. -
-type 'a intension = s -> 'a;;
-let unit x (w:s) = x;;
+	type 'a intension = s -> 'a;;
+	let unit x (w:s) = x;;
 
-let ann = unit 'a';;
-let bill = unit 'b';;
-let cam = unit 'c';;
-
+ let ann = unit 'a';; + let bill = unit 'b';; + let cam = unit 'c';; In our monad, the intension of an extensional type `'a` is `s -> 'a`, a function from worlds to extensions. Our unit will be the constant @@ -540,12 +530,10 @@ as an argument. Let's test compliance with the left identity law: -
-# let bind u f (w:s) = f (u w) w;;
-val bind : (s -> 'a) -> ('a -> s -> 'b) -> s -> 'b = 
-# bind (unit 'a') unit 1;;
-- : char = 'a'
-
+ # let bind u f (w:s) = f (u w) w;; + val bind : (s -> 'a) -> ('a -> s -> 'b) -> s -> 'b = + # bind (unit 'a') unit 1;; + - : char = 'a' We'll assume that this and the other laws always hold. @@ -560,15 +548,13 @@ Then the way to evaluate an extensional sentence is to determine the extension of the verb phrase, and then apply that extension to the extension of the subject: -
-let extapp fn arg w = fn w (arg w);;
+	let extapp fn arg w = fn w (arg w);;
 
-extapp left ann 1;;
-# - : bool = true
+	extapp left ann 1;;
+	# - : bool = true
 
-extapp left cam 2;;
-# - : bool = false
-
+ extapp left cam 2;; + # - : bool = false `extapp` stands for "extensional function application". So Ann left in world 1, but Cam didn't leave in world 2. @@ -599,12 +585,10 @@ Just as we used bind to define a version of addition that interacted with the option monad, we now use bind to intensionalize an extensional verb: -
-let lift pred w arg = bind arg (fun x w -> pred w x) w;;
+	let lift pred w arg = bind arg (fun x w -> pred w x) w;;
 
-intapp (lift left) ann 1;; (* true: Ann still left in world 1 *)
-intapp (lift left) cam 2;; (* false: Cam still didn't leave in world 2 *)
-
+ intapp (lift left) ann 1;; (* true: Ann still left in world 1 *) + intapp (lift left) cam 2;; (* false: Cam still didn't leave in world 2 *) Because `bind` unwraps the intensionality of the argument, when the lifted "left" receives an individual concept (e.g., `unit 'a'`) as @@ -615,12 +599,10 @@ third meaning postulate.) Likewise for extensional transitive predicates like "saw": -
-let lift2 pred w arg1 arg2 = 
-  bind arg1 (fun x -> bind arg2 (fun y w -> pred w x y)) w;;
-intapp (intapp (lift2 saw) bill) ann 1;;  (* true: Ann saw Bill in world 1 *)
-intapp (intapp (lift2 saw) bill) ann 2;;  (* false: No one saw anyone in world 2 *)
-
+ let lift2 pred w arg1 arg2 = + bind arg1 (fun x -> bind arg2 (fun y w -> pred w x y)) w;; + intapp (intapp (lift2 saw) bill) ann 1;; (* true: Ann saw Bill in world 1 *) + intapp (intapp (lift2 saw) bill) ann 2;; (* false: No one saw anyone in world 2 *) Crucially, an intensional predicate does not use `bind` to consume its arguments. Attitude verbs like "thought" are intensional with respect @@ -628,22 +610,18 @@ to their sentential complement, but extensional with respect to their subject (as Montague noticed, almost all verbs in English are extensional with respect to their subject; a possible exception is "appear"): -
-let think (w:s) (p:s->t) (x:e) = 
-  match (x, p 2) with ('a', false) -> false | _ -> p w;;
-
+ let think (w:s) (p:s->t) (x:e) = + match (x, p 2) with ('a', false) -> false | _ -> p w;; Ann disbelieves any proposition that is false in world 2. Apparently, she firmly believes we're in world 2. Everyone else believes a proposition iff that proposition is true in the world of evaluation. -
-intapp (lift (intapp think
-                     (intapp (lift left)
-                             (unit 'b'))))
-       (unit 'a') 
-1;; (* true *)
-
+ intapp (lift (intapp think + (intapp (lift left) + (unit 'b')))) + (unit 'a') + 1;; (* true *) So in world 1, Ann thinks that Bill left (because in world 2, Bill did leave). @@ -651,13 +629,11 @@ The `lift` is there because "think Bill left" is extensional wrt its subject. The important bit is that "think" takes the intension of "Bill left" as its first argument. -
-intapp (lift (intapp think
-                     (intapp (lift left)
-                             (unit 'c'))))
-       (unit 'a') 
-1;; (* false *)
-
+ intapp (lift (intapp think + (intapp (lift left) + (unit 'c')))) + (unit 'a') + 1;; (* false *) But even in world 1, Ann doesn't believe that Cam left (even though he did: `intapp (lift left) cam 1 == true`). Ann's thoughts are hung up @@ -672,8 +648,8 @@ on what is happening in world 2, where Cam doesn't leave. Finally, note that within an intensional grammar, extensional funtion application is essentially just bind: -
-# let swap f x y = f y x;;
-# bind cam (swap left) 2;;
-- : bool = false
-
+ # let swap f x y = f y x;; + # bind cam (swap left) 2;; + - : bool = false + + -- 2.11.0