coroutines tweak
[lambda.git] / coroutines_and_aborts.mdwn
index 0afd239..c6b5f45 100644 (file)
@@ -364,15 +364,15 @@ Here we call `foo bar 0`, and `foo` in turn calls `bar 0`, and `bar` raises the
 OK, now this exception-handling apparatus does exemplify the second execution pattern we want to focus on. But it may bring it into clearer focus if we simplify the pattern even more. Imagine we could write code like this instead:
 
        # let foo x =
-           try
+           try begin
                (if x = 1 then 10
-               else abort 20) + 1
+               else abort 20) + 100
            end
            ;;
 
-then if we called `foo 1`, we'd get the result `11`. If we called `foo 2`, on the other hand, we'd get `20` (note, not `21`). This exemplifies the same interesting "jump out of this part of the code" behavior that the `try ... raise ... with ...` code does, but without the details of matching which exception was raised, and handling the exception to produce a new result.
+then if we called `foo 1`, we'd get the result `110`. If we called `foo 2`, on the other hand, we'd get `20` (note, not `120`). This exemplifies the same interesting "jump out of this part of the code" behavior that the `try ... raise ... with ...` code does, but without the details of matching which exception was raised, and handling the exception to produce a new result.
 
-Many programming languages have this simplified exceution pattern, either instead of or alongside a `try ... with ...`-like pattern. In Lua and many other languages, `abort` is instead called `return`. The preceding example would be written:
+Many programming languages have this simplified exceution pattern, either instead of or alongside a `try ... with ...`-like pattern. In Lua and many other languages, `abort` is instead called `return`. In Lua, the preceding example would be written:
 
        > function foo(x)
            local value
@@ -381,11 +381,11 @@ Many programming languages have this simplified exceution pattern, either instea
            else
                return 20
            end
-           return value + 1
+           return value + 100
        end
        
        > return foo(1)
-       11
+       110
        
        > return foo(2)
        20
@@ -399,22 +399,20 @@ In both of these patterns, we need to have some way to take a snapshot of where
 A more general way to think about these snapshots is to think of the code we're taking a snapshot of as a *function.* For example, in this code:
 
        let foo x =
-           try
+           try begin
                (if x = 1 then 10
-               else abort 20) + 1
+               else abort 20) + 100
            end
        in (foo 2) + 1;;
 
 we can imagine a box:
 
        let foo x =
-       +---------------------------+
-       |   try                     |
-       |       (if x = 1 then 10   |
-       |       else abort 20) + 1  |
-       |   end                     |
-       +---------------------------+
-       in (foo 2) + 1;;
+       +---try begin----------------+
+       |       (if x = 1 then 10    |
+       |       else abort 20) + 100 |
+       +---end----------------------+
+       in (foo 2) + 1000;;
 
 and as we're about to enter the box, we want to take a snapshot of the code *outside* the box. If we decide to abort, we'd be aborting to that snapshotted code.
 
@@ -423,10 +421,85 @@ and as we're about to enter the box, we want to take a snapshot of the code *out
 # open Delimcc;;
 # let reset body = let p = new_prompt () in push_prompt p (body p);;
 val reset : ('a Delimcc.prompt -> unit -> 'a) -> 'a = <fun>
-# let foo x = reset(fun p () -> (shift p (fun k -> if x = 1 then k 10 else 20)) + 1) in (foo 1) + 100;;
-- : int = 111
-# let foo x = reset(fun p () -> (shift p (fun k -> if x = 1 then k 10 else 20)) + 1) in (foo 2) + 100;;
-- : int = 120
+# let foo x = reset(fun p () -> (shift p (fun k -> if x = 1 then k 10 else 20)) + 100) in (foo 1) + 1000;;
+- : int = 1110
+# let foo x = reset(fun p () -> (shift p (fun k -> if x = 1 then k 10 else 20)) + 100) in (foo 2) + 1000;;
+- : int = 1020
 -->
 
+What would a "snapshot of the code outside the box" look like? Well, let's rearrange the code somewhat. It should be equivalent to this:
+
+       let x = 2 in
+       let foo_result =
+       +---try begin----------------+
+       |       (if x = 1 then 10    |
+       |       else abort 20) + 100 |
+       +---end----------------------+
+       in (foo_result) + 1000;;
+
+and we can think of the code starting with `let foo_result = ...` as a function, with the box being its parameter, like this:
+
+       fun box ->
+               let foo_result = box
+               in (foo_result) + 1000
+
+That function is our "snapshot". Normally what happens is that code *inside* the box delivers up a value, and that value gets supplied as an argument to the snapshot-function just described. That is, our code is essentially working like this:
+
+       let x = 2
+       in let snapshot = fun box ->
+               let foo_result = box
+               in (foo_result) + 1000
+       in let value =
+               (if x = 1 then 10
+               else ... (* we'll come back to this part *)
+               ) + 100
+       in shapshot value;;
+
+But now how should the `abort 20` part, that we ellided here, work? What should happen when we try to evaluate that?
+
+Well, that's when we use the snapshot code in an unusual way. If we encounter an `abort 20`, we should abandon the code we're currently executing, and instead just supply `20` to the snapshot we saved when we entered the box. That is, something like this:
+
+       let x = 2
+       in let snapshot = fun box ->
+               let foo_result = box
+               in (foo_result) + 1000
+       in let value =
+               (if x = 1 then 10
+               else snapshot 20
+               ) + 100
+       in shapshot value;;
+
+Except that isn't quite right, yet---in this fragment, after the snapshot code is finished, we'd pick up again inside `let value = (...) + 100 in snapshot value`. We don't want to pick up again there. We want instead to do this:
+
+       let x = 2
+       in let snapshot = fun box ->
+               let foo_result = box
+               in (foo_result) + 1000
+       in let value =
+               (if x = 1 then 10
+               else snapshot 20 THEN STOP
+               ) + 100
+       in shapshot value;;
+
+We can get that by some further rearranging of the code:
+
+       let x = 2
+       in let snapshot = fun box ->
+               let foo_result = box
+               in (foo_result) + 1000
+       in let finish_value = fun start ->
+               let value = start + 100
+               in snapshot value
+       in 
+               if x = 1 then finish_value 10
+               else snapshot 20;;
+
+And this is indeed what is happening, at a fundamental level, when you use an expression like `abort 20`.
+
+A similar kind of "snapshotting" lets coroutines keep track of where the left off, so that they can start up again at that same place.
+
+These snapshots are called **continuations** because they represent how the computation will "continue" once some target code (in our example, the code in the box) delivers up a value.
+
+You can think of them as functions that represent "how the rest of the computation proposes to continue." Except that, once we're able to get our hands on those functions, we can do exotic and unwholesome things with them. Like use them to abort from deep inside a sub-computation. Or suspend and resume a thread. One function might pass the command to abort *it* to a subfunction, so that the subfunction has the power to jump directly to the outside caller. Or a function might *return* its continuation function to the outside caller, giving the outside caller the ability to "abort" the function (that has already returned its value---what should happen then?) Maybe we'd call the same continuation function *multiple times* (what should happen then?). All of these weird and wonderful possibilities await us.
+