df757cbce051c1845dad7fc420f93b548552d0bb
[lambda.git] / hints / assignment_6_commentary.mdwn
1 Many of you offered a solution along the following lines:
2
3         type 'a state = int -> 'a * int;;
4         let unit (a : 'a) : 'a state =
5           fun count -> (a, count);;
6         let bind (u : 'a state) (f : 'a -> 'b state ) : 'b state =
7           fun count -> let (a, count') = u count in f a count';;
8
9         (* Looks good so far, now how are we going to increment the count? *)
10
11         let lift2 (f : 'a -> 'b -> 'c) (u : 'a state) (v : 'b state) : 'c state =
12           bind u (fun x ->
13             bind v (fun y ->
14               fun count -> (f x y, count + 1)));;
15
16 Whoops. That will work for the cases you're probably thinking about. For instance, you can do:
17
18         lift2 (+) (unit 1) (lift2 (+) (unit 2) (unit 3));;
19
20 and you'll get back an `int state` that when applied to a starting count of `0` yields the result `(6, 2)`---that is, the result of the computation was 6 and the number of operations was 2.
21
22 However, there are several problems here. First off, you shouldn't name your function `lift`, because we're using that name for a function that's interdefinable with `bind` in a specific way. Our canonical `lift` function (also called `mapM` and `liftM` in Haskell) is:
23
24         let lift2 (f : 'a -> 'b -> 'c) (u : 'a state) (v : 'b state) : 'c state =
25           bind u (fun x ->
26             bind v (fun y ->
27               unit (f x y)));;
28
29 OK, so then you might call your function `loft2` instead. So what?
30
31 The remaining problem is more subtle. It's that your solution isn't very modular. You've crafted a tool `loft2` that fuses the operation of incrementing the count with the behavior of our `lift2`. What if we needed to deal with some unary functions as well? Then you'd need a `loft1`. What if we need to deal with some functions that are already monadic? Then you'd need a tool that fuses the count-incrementing with the behavior of `bind`. And so on.
32
33 It's nicer to just create a little module that does the count-incrementing, and then use that together with the pre-existing apparatus of `bind` and (our canonical) `lift` and `lift2`. You could do that like this:
34
35         let tick (a : 'a) : 'a state =
36           fun count -> (a, count + 1);;
37         
38         let result1 =
39           bind
40             (lift2 (+)
41               (unit 1)
42               (bind
43                 (lift2 (+)
44                   (unit 2)
45                   (unit 3))
46                 tick))
47             tick;;
48         
49         result1 0;; (* evaluates to (6, 2) *)
50
51 Or like this:
52
53         let tock : unit state =
54           fun count -> ((), count + 1);;
55         
56         let result2 =
57           bind
58             tock
59             (fun _ -> lift2 (+)
60               (unit 1)
61               (bind
62                 tock
63                 (fun _ -> lift2 (+)
64                   (unit 2)
65                   (unit 3))));;
66         
67         result2 0;; (* evaluates to (6, 2) *)
68