X-Git-Url: http://lambda.jimpryor.net/git/gitweb.cgi?p=lambda.git;a=blobdiff_plain;f=monad_transformers.mdwn;h=086a2ffc9e227a7a9c55424a3ebc07e948139ddc;hp=a1e6d45efce280ea0a963e35c16cd827ea740d75;hb=e948c27044f69962483a6c62a8568b84acd5be85;hpb=de7a141b23248017c50e05b407e7b5550eedcb3c;ds=sidebyside diff --git a/monad_transformers.mdwn b/monad_transformers.mdwn index a1e6d45e..086a2ffc 100644 --- a/monad_transformers.mdwn +++ b/monad_transformers.mdwn @@ -252,7 +252,7 @@ then `list_bind u f` would be `concat [[]; [2]; [2; 4]; [2; 4; 8]]`, that is `[2 | | 4 8 -Except, as we mentioned, our implementation of the Tree monad incorporates an Optionish layer too. So `f' 2` should be not `Leaf 2` but `Some (Leaf 2)`. What if `f'` also mapped `1` to `None` and `4` to `Some (Node (Leaf 2, Leaf 4))`. Then binding the tree `Node (Leaf 1, Node (Leaf 2, Leaf 4))` to `f'` would delete thr branch corresponding to the original `Leaf 1`, and would splice in the results for `f' 2` and `f' 4`, yielding: +Except, as we mentioned, our implementation of the Tree monad incorporates an Optionish layer too. So `f' 2` should be not `Leaf 2` but `Some (Leaf 2)`. What if `f'` also mapped `1` to `None` and `4` to `Some (Node (Leaf 2, Leaf 4))`. Then binding the tree `Node (Leaf 1, Node (Leaf 2, Leaf 4))` (really the tree itself needs to be wrapped in a `Some`, too, but let me neglect that) to `f'` would delete the branch corresponding to the original `Leaf 1`, and would splice in the results for `f' 2` and `f' 4`, yielding: . _|__ >>= f' ~~> @@ -287,15 +287,203 @@ You have to instead say something like this: How is all this related to our tree\_monadize function? ------------------------------------------------------- +Our Tree monad has a corresponding TreeT transformer. Simplified, its implementation looks something like this (we apply it to an inner Reader monad): + + + type 'a tree_reader = 'a tree reader;; + (* really it's an 'a tree option reader, but as I said we're simplifying *) + + let tree_reader_unit (a:'a) : 'a tree_reader = reader_unit (Leaf a);; + + let tree_reader_bind (u: 'a tree_reader) (f: 'a -> 'b tree_reader) : 'b tree_reader = + reader_bind u (fun us -> + let rec loop us = match us with + | Leaf a -> + f a + | Node(l,r) -> + reader_bind (loop l) (fun ls -> + reader_bind (loop r) (fun rs -> + reader_unit (Node(ls, rs)))) + in loop us);; + + let tree_reader_elevate (inner : 'a reader) : 'a tree_reader = + reader_bind inner (fun a -> reader_unit (Leaf a)) + Recall our earlier definition of `tree_monadize`, specialized for the Reader monad: let rec tree_monadize (f : 'a -> 'b reader) (t : 'a tree) : 'b tree reader = match t with - | Leaf a -> reader_bind (f a) (fun b -> reader_unit (Leaf b)) - | Node (l, r) -> reader_bind (tree_monadize f l) (fun l' -> - reader_bind (tree_monadize f r) (fun r' -> - reader_unit (Node (l', r'))));; + | Leaf a -> + (* the next line is equivalent to: tree_reader_elevate (f a) *) + reader_bind (f a) (fun b -> reader_unit (Leaf b)) + | Node (l, r) -> + reader_bind (tree_monadize f l) (fun ls -> + reader_bind (tree_monadize f r) (fun rs -> + reader_unit (Node (ls, rs))));; + +We rendered the result type here as `'b tree reader`, as we did in our earlier discussion, but as we can see from the above implementation of TreeT(Reader), that's the type of an `'b tree_reader`, that is, of a layered box consisting of TreeT packaging wrapped around an inner Reader box. + +The definitions of `tree_monadize` and `tree_reader_bind` should look very similar. They're not quite the same. There's the difference in the order of their function-like and tree-like arguments, but that's inconsequential. More important is that the types of their arguments differs. `tree_reader_bind` wants a tree that's already fused with a reader; `tree_monadize` instead just wants a plain tree. `tree_reader_bind` wants a function that takes the elements occupying its leaves into other `tree_reader`s; `tree_monadize` just wants it to take them into plain `reader`s. That's why the application of `f` to `a` has to be `elevate`d in the `tree_monadize` clause for `Leaf a -> ...`. + +But there is an obvious common structure to these two functions, and indeed in the [[monad library]] their more complicated cousins are defined in terms of common pieces. In the monad library, the `tree_monadize` function is called `distribute`; this is an operation living inside the TreeT packaging. There's an analogous `distribute` function living inside the ListT packaging. (Haskell has the second but not the first; it calls it `mapM` and it lives inside the wrapped base monad, instead of the List packaging.) + +We linked to [some code](/code/tree_monadize.ml) earlier that demonstrated all the `tree_monadize` examples in a compact way. + +Here's how to demonstrate the same examples, using the monad library. First, preliminaries: + + # #use "path/to/monads.ml";; + # module T = Tree_monad;; + # module R = Reader_monad(struct type env = int -> int end);; + # module S = State_monad(struct type store = int end);; + # module L = List_monad;; + # module C = Continuation_monad;; + # module TR = T.T(R);; + # module TS = T.T(S);; + # module TL = T.T(L);; + # module TC = T.T(C);; + # let t1 = Some (T.Node (T.Node (T.Leaf 2, T.Leaf 3), T.Node (T.Leaf 5, T.Node (T.Leaf 7, T.Leaf 11))));; + +We can use TreeT(Reader) to modify leaves: + + # let tree_reader = TR.distribute (fun i -> R.asks (fun e -> e i)) t1;; + # TR.run tree_reader (fun i -> i+i);; + (* + - : int T.tree option = + Some + (T.Node + (T.Node (T.Leaf 4, T.Leaf 6), + T.Node (T.Leaf 10, T.Node (T.Leaf 14, T.Leaf 22)))) + *) + +Here's a comparison of how distribute works for trees and how it works for lists: + + # module LR = L.T(R);; + # let l1 = [2; 3; 5; 7; 11];; + # LR.(run (distribute (fun i -> R.(asks (fun e -> e i))) l1)) (fun i -> i+i);; + - : int list = [4; 6; 10; 14; 22] + + + +We can use TreeT(State) to count leaves: + + # let tree_counter = TS.distribute (fun i -> S.(puts succ >> unit i)) t1 in + TS.run tree_counter 0;; + (* + - : int T.tree option * S.store = + (Some + (T.Node + (T.Node (T.Leaf 2, T.Leaf 3), + T.Node (T.Leaf 5, T.Node (T.Leaf 7, T.Leaf 11)))), + 5) + *) + +or to annotate leaves: + + # let tree_annotater = TS.distribute (fun i -> S.(puts succ >> get >>= fun s -> unit (i,s))) t1 in + TS.run tree_annotater 0;; + - : (int * S.store) T.tree option * S.store = + (Some + (T.Node + (T.Node (T.Leaf (2, 1), T.Leaf (3, 2)), + T.Node (T.Leaf (5, 3), T.Node (T.Leaf (7, 4), T.Leaf (11, 5))))), + 5) + +Here's a comparison of how distribute works for trees and how it works for lists: + + # module LS = L.T(S);; + + # let list_counter = LS.distribute (fun i -> S.(puts succ >> unit i)) l1 in + LS.run list_counter 0;; + - : int list * S.store = ([2; 3; 5; 7; 11], 5) + + # let list_annotater = LS.distribute (fun i -> S.(puts succ >> get >>= fun s -> unit (i,s) )) l1 in + LS.run list_annotater 0;; + - : (int * S.store) list * S.store = + ([(2, 1); (3, 2); (5, 3); (7, 4); (11, 5)], 5) + + + + + +We can use TreeT(List) to copy the tree with different choices for some of the leaves: + + # let tree_chooser = TL.distribute (fun i -> L.(if i = 2 then plus (unit 20) (unit 21) else unit i)) t1;; + # TL.run tree_chooser;; + - : ('_a, int) TL.result = + [Some + (T.Node + (T.Node (T.Leaf 20, T.Leaf 3), + T.Node (T.Leaf 5, T.Node (T.Leaf 7, T.Leaf 11)))); + Some + (T.Node + (T.Node (T.Leaf 21, T.Leaf 3), + T.Node (T.Leaf 5, T.Node (T.Leaf 7, T.Leaf 11))))] + + +Finally, we use TreeT(Continuation) to do various things. For reasons I won't explain here, the library currently requires you to run the Tree-plus-Continuation bundle using a different sequence of `run` commands: + +We can do nothing: + + + # C.run_exn TC.(run (distribute C.unit t1)) (fun t -> t);; + - : int T.tree option = + Some + (T.Node + (T.Node (T.Leaf 2, T.Leaf 3), + T.Node (T.Leaf 5, T.Node (T.Leaf 7, T.Leaf 11)))) + +We can square each leaf. The meaning of `shift` will be explained in [[CPS and Continuation Operators]]. + + + # C.run_exn TC.(run (distribute C.(fun a -> shift (fun k -> k (a*a))) t1)) (fun t -> t);; + - : int T.tree option = + Some + (T.Node + (T.Node (T.Leaf 4, T.Leaf 9), + T.Node (T.Leaf 25, T.Node (T.Leaf 49, T.Leaf 121)))) + +We can count the leaves: + + + # C.run_exn TC.(run (distribute C.(fun a -> shift (fun k -> k a >>= fun v -> unit (1+v))) t1)) (fun t -> 0);; + - : int = 5 + + +We can convert the tree to a list of leaves: + + # C.run_exn TC.(run (distribute C.(fun a -> shift (fun k -> k a >>= fun v -> unit (a::v))) t1)) (fun t -> []);; + - : int list = [2; 3; 5; 7; 11] -(MORE...)