X-Git-Url: http://lambda.jimpryor.net/git/gitweb.cgi?p=lambda.git;a=blobdiff_plain;f=code%2Ftree_monadize.ml;h=d9c5a5900e520c3fb78c918dd0abbd08f41ea8ae;hp=310b797e4f08c0bcc631194dfcd1a5f488bbc5dc;hb=0c48836d5dbaa454898800a73183d6775cc3664c;hpb=f6950034eb1c228badf3364375595032a56e3afb diff --git a/code/tree_monadize.ml b/code/tree_monadize.ml index 310b797e..d9c5a590 100644 --- a/code/tree_monadize.ml +++ b/code/tree_monadize.ml @@ -1,30 +1,105 @@ (* * tree_monadize.ml * - * 'a and so on are type variables in OCaml; they stand for arbitrary types + * If you've got some block of code that uses `unit`s and `bind`s, and you + * want to interpret it alternately using this monad, that monad, or another + * monad, you can use OCaml's module system. You'd write your code like this: + *) + +module Reader_monad = struct + (* change this to suit your needs *) + type env = int -> int;; + + type 'a m = env -> 'a;; + let unit a : 'a m = fun e -> a;; + let bind (u : 'a m) (f : 'a -> 'b m) : 'b m = + fun e -> f (u e) e;; +end + +module State_monad = struct + (* change this to suit your needs *) + type store = int;; + + type 'a m = store -> 'a * store;; + let unit a : 'a m = fun s -> (a, s);; + let bind (u : 'a m) (f : 'a -> 'b m) : 'b m = + fun s -> (let (a, s') = u s in (f a) s');; +end + +module List_monad = struct + type 'a m = 'a list;; + let unit a : 'a m = [a];; + let bind (u: 'a m) (f : 'a -> 'b m) : 'b m = + List.concat(List.map f u);; +end + +(* + * Then you can replace code that looks like this: + * ... reader_bind ... + * with code that looks like this: + * ... Reader_monad.bind ... + * and the latter can be reformulated like this: + * let open Reader_monad in ... bind ... + * or equivalently, like this: + * Reader_monad.(... bind ...) + * Then you can use literally the same `... bind ...` code when writing instead: + * State_monad.(... bind ...) + *) + +(* That's great, however it still requires us to repeat the + * `... bind ...` code every time we want to change which monad we're working + * with. Shouldn't there be a way to _parameterize_ the `... bind ...` code + * on a monad, so that we only have to write the `... bind ...` code once, + * but can invoke it alternately with the Reader_monad supplied as an + * argument, or the State_monad, or another? + * + * There is a way to do this, but it requires putting the `... bind ...` code in + * its own module, and making that module parameterized on some M_monad + * module. Also we have to explicitly declare what commonality we're expecting + * from M_monad modules we're going to use as parameters. We'll explain how to + * do this in a moment. + * + * As preparation, a general observation: + * 'a and so on are type variables in OCaml; they stand for arbitrary types. * What if you want a variable for a type constructor? For example, you want to * generalize this pattern: - * type ('a) t1 = 'a -> 'a list - * type ('a) t2 = 'a -> 'a option - * type ('a) t3 = 'a -> 'a reader + * type ('a) t1 = 'a -> ('a) list + * type ('a) t2 = 'a -> ('a) option + * type ('a) t3 = 'a -> ('a) reader * and so on? OCaml won't let you do this: - * type ('a, 'b) t = 'a -> 'a 'b - * to generalize on the 'b position, we instead have to use OCaml's modules, + * type ('a, 'b) t = 'a -> ('a) 'b + * To generalize on the 'b position, we instead have to use OCaml's modules, * and in particular its ability to make modules parameterized on other modules * (OCaml calls these parameterized modules Functors, but that name is also * used in other ways in this literature, so I won't give in to it.) * * Here's how you'd have to define the t type from above: - * module T_maker(S: sig - * type 'a b - * end) = struct - * type 'a t = 'a -> 'a S.b + * module T_maker( + * (* A sig...end block specifies the type of a module + * * What we're doing here is specifying the type of the + * * module parameter that will choose + * * whether b = list or b = option or b = reader... + * * This module parameter may supply values as well as types *) + * M : sig + * type ('a) b + * end + * ) = + * (* A struct...end block gives a module value + * * What we're doing here is building a new module that makes + * * use of the module that was supplied as M *) + * struct + * type ('a) t = 'a -> ('a) M.b * end * And here's how you'd use it: * module T_list = T_maker(struct type 'a b = 'a list end);; * type 'a t1 = 'a T_list.t;; + * module T_option = T_maker(struct type 'a b = 'a option end);; + * type 'a t2 = 'a T_option.t;; + * (* and so on *) * - * I know, it seems unnecessarily complicated. + * I know, it seems unnecessarily complicated. Nonetheless, that's how it + * works. And that is also the technique we'll use to make our + * `... bind ...` code parametric on some M_monad module. *) type 'a tree = Leaf of 'a | Node of ('a tree) * ('a tree);; @@ -38,87 +113,68 @@ let t1 = Node (Leaf 7, Leaf 11)));; -module Tree_monadizer(S: sig +module Tree_monadizer(M : sig + (* the module we're using as a parameter has to supply function values + * for unit and bind, as well as a monadic type constructor *) type 'a m val unit : 'a -> 'a m val bind : 'a m -> ('a -> 'b m) -> 'b m end) = struct - let rec monadize (f: 'a -> 'b S.m) (t: 'a tree) : 'b tree S.m = + let rec monadize (f: 'a -> 'b M.m) (t: 'a tree) : 'b tree M.m = match t with - | Leaf a -> S.bind (f a) (fun b -> S.unit (Leaf b)) + | Leaf a -> M.bind (f a) (fun b -> M.unit (Leaf b)) | Node(l, r) -> - S.bind (monadize f l) (fun l' -> - S.bind (monadize f r) (fun r' -> - S.unit (Node (l', r')))) + M.bind (monadize f l) (fun l' -> + M.bind (monadize f r) (fun r' -> + M.unit (Node (l', r')))) end;; -type env = int -> int;; +(* Now we supply Reader_monad as a parameter to Tree_monadizer. + * We'll get back a module TreeReader that contains a single value, + * the monadize function specialized to the Reader monad *) +module TreeReader = Tree_monadizer(Reader_monad);; -type 'a reader = env -> 'a;; -let unit_reader a : 'a reader = fun e -> a;; -let bind_reader (u : 'a reader) (f : 'a -> 'b reader) : 'b reader = - fun e -> f (u e) e;; -module TreeReader = Tree_monadizer(struct - type 'a m = 'a reader - let unit = unit_reader - let bind = bind_reader -end);; +(* Make a TreeState module containing monadize specialized to the State monad *) +module TreeState = Tree_monadizer(State_monad);; -type store = int;; +(* Make a TreeList module containing monadize specialized to the List monad *) +module TreeList = Tree_monadizer(List_monad);; -type 'a state = store -> 'a * store;; -let unit_state a : 'a state = fun s -> (a, s);; -let bind_state (u : 'a state) (f : 'a -> 'b state) : 'b state = - fun s -> (let (a, s') = u s in (f a) s');; -module TreeState = Tree_monadizer(struct - type 'a m = 'a state - let unit = unit_state - let bind = bind_state -end);; +(* The Continuation monad is a bit more complicated *) +module Continuation_monad = struct + type ('r,'a) m = ('a -> 'r) -> 'r;; + let unit a : ('r,'a) m = fun k -> k a;; + let bind (u: ('r,'a) m) (f: 'a -> ('r,'b) m) : ('r,'b) m = + fun k -> u (fun a -> f a k);; +end - -let unit_list a = [a];; -let bind_list (u: 'a list) (f : 'a -> 'b list) : 'b list = - List.concat(List.map f u);; - -module TreeList = Tree_monadizer(struct - type 'a m = 'a list - let unit = unit_list - let bind = bind_list -end);; - - - -(* we need to a new module when the monad is parameterized on two types *) -module Tree_monadizer2(S: sig - type ('a,'x) m - val unit : 'a -> ('a,'x) m - val bind : ('a,'x) m -> ('a -> ('b,'x) m) -> ('b,'x) m +(* Since the Continuation monad is parameterized on two types---it's + * ('r,'a) cont not ('a) cont---we can't match the type ('a) monad that + * Tree_monadizer expects in its parameter. So we have to make a different + * Tree_monadizer2 that takes a ('r,'a) monad type constructor in its + * parameter instead *) +module Tree_monadizer2(M : sig + type ('r,'a) m + val unit : 'a -> ('r,'a) m + val bind : ('r,'a) m -> ('a -> ('r,'b) m) -> ('r,'b) m end) = struct - let rec monadize (f: 'a -> ('b,'x) S.m) (t: 'a tree) : ('b tree,'x) S.m = - (* the definition is the same, the difference is only in the types *) + (* the body of the monadize function is the same; the only difference is in + * the types *) + let rec monadize (f: 'a -> ('r,'b) M.m) (t: 'a tree) : ('r,'b tree) M.m = match t with - | Leaf a -> S.bind (f a) (fun b -> S.unit (Leaf b)) + | Leaf a -> M.bind (f a) (fun b -> M.unit (Leaf b)) | Node(l, r) -> - S.bind (monadize f l) (fun l' -> - S.bind (monadize f r) (fun r' -> - S.unit (Node (l', r')))) + M.bind (monadize f l) (fun l' -> + M.bind (monadize f r) (fun r' -> + M.unit (Node (l', r')))) end;; -type ('a,'r) cont = ('a -> 'r) -> 'r;; -let unit_cont a : ('a,'r) cont = fun k -> k a;; -let bind_cont (u: ('a,'r) cont) (f: 'a -> ('b,'r) cont) : ('b,'r) cont = - fun k -> u (fun a -> f a k);; - -module TreeCont = Tree_monadizer2(struct - type ('a,'r) m = ('a,'r) cont - let unit = unit_cont - let bind = bind_cont -end);; +(* Make a TreeCont module containing monadize specialized to the Cont monad *) +module TreeCont = Tree_monadizer2(Continuation_monad);; @@ -127,34 +183,89 @@ end);; * http://lambda.jimpryor.net/manipulating_trees_with_monads/ *) -let int_readerize : int -> int reader = - fun (a : int) (modifier : int -> int) -> modifier a;; + +let asker : int -> int Reader_monad.m = + fun (a : int) -> fun (env : int -> int) -> env a;; + +(* asker takes an int and returns a Reader monad that + * "looks up" that int in the environment (i.e. modifies it) + * this is structurally parallel to the function `lookup` we used + * before to "look up" variables in the environment *) (* double each leaf *) -TreeReader.monadize int_readerize t1 (fun i -> i + i);; +let env = fun i -> i + i in +TreeReader.monadize asker t1 env;; + +(* You can also avoid declaring a separate toplevel TreeReader module + * (or even a separate Reader_monad module) by using one of these forms: + * ... + * let module T = Tree_monadizer(Reader_monad) in + * T.monadize asker t1 env;; + * or: + * ... + * let env = fun i -> i + i in + * let module Monad = struct + * type env = int -> int;; + * type 'a m = env -> 'a;; + * let unit a : 'a m = fun e -> a;; + * let bind (u : 'a m) (f : 'a -> 'b m) : 'b m = + * fun e -> f (u e) e;; + * end in + * let module T = Tree_monadizer(Monad) in + * T.monadize asker t1 env;; + * or: + * ... + * let module T = Tree_monadizer(struct + * type env = int -> int;; + * type 'a m = env -> 'a;; + * let unit a : 'a m = fun e -> a;; + * let bind (u : 'a m) (f : 'a -> 'b m) : 'b m = + * fun e -> f (u e) e;; + * end) in + * T.monadize asker t1 env;; + *) + (* square each leaf *) -TreeReader.monadize int_readerize t1 (fun i -> i * i);; +let env = fun i -> i * i in +TreeReader.monadize asker t1 env;; + + + +let incrementer : int -> int State_monad.m = + fun (a : int) -> fun s -> (a, s+1);; + +(* incrementer takes an 'a and returns it wrapped in a + * State monad that increments the store *) (* count leaves *) -TreeState.monadize (fun a s -> (a, s+1)) t1 0;; +let initial_store = 0 in +TreeState.monadize incrementer t1 initial_store;; + + (* replace leaves with list *) -TreeList.monadize (fun i -> [[i;i*i]]) t1;; +TreeList.monadize (fun i -> [ [i;i*i] ]) t1;; + -(* convert tree to list of leaves *) -TreeCont.monadize (fun a k -> a :: k a) t1 (fun t -> []);; (* do nothing *) -TreeCont.monadize unit_cont t1 (fun t-> t);; +let initial_continuation = fun t -> t in +TreeCont.monadize Continuation_monad.unit t1 initial_continuation;; + +(* convert tree to list of leaves *) +let initial_continuation = fun t -> [] in +TreeCont.monadize (fun a k -> a :: k a) t1 initial_continuation;; (* square each leaf using continuation *) -TreeCont.monadize (fun a k -> k (a*a)) t1 (fun t -> t);; +let initial_continuation = fun t -> t in +TreeCont.monadize (fun a k -> k (a*a)) t1 initial_continuation;; (* replace leaves with list, using continuation *) -TreeCont.monadize (fun a k -> k [a; a*a]) t1 (fun t -> t);; +let initial_continuation = fun t -> t in +TreeCont.monadize (fun a k -> k [a; a*a]) t1 initial_continuation;; (* count leaves, using continuation *) -TreeCont.monadize (fun a k -> 1 + k a) t1 (fun t -> 0);; - +let initial_continuation = fun t -> 0 in +TreeCont.monadize (fun a k -> 1 + k a) t1 initial_continuation;;