revisions
authorjim <jim@web>
Thu, 30 Apr 2015 15:27:00 +0000 (11:27 -0400)
committerLinux User <ikiwiki@localhost.members.linode.com>
Thu, 30 Apr 2015 15:27:00 +0000 (11:27 -0400)
topics/week13_control_operators.mdwn

index 325c2a2..ce68d11 100644 (file)
@@ -27,7 +27,7 @@ The first two lines aren't very different from what we'd have without mutation:
     let x = 0 in
     ... x ...
 
-The first line used the keyword `var` instead of the more familiar `let`, but that's just to signal that the variable we're introducing is mutable. Syntactically it acts just like a variant spelling of `let`. Also we access the contents of the variable in the same way, with just `x`. Whereas with the explicit reference cells, we have to say `get x`. We can "see" the reference cell and have to explicitly "look inside it" to get at its contents. That's like seeing the "new game" button and other controls during the normal use of the video game. Then in the third line of the implicit mutation code, we have the "magic gesture", `x := 1`, which does something you couldn't do in the code without mutation. This is like bringing up the "new game" display by the magic elbow-and-stomping gesture, which doesn't work in real life. This lets us achieve the same effect that we did in the explicit code using `put 1 into x`, but without us needing to (or being able to) explicitly inspect or manipulate the reference cell itself.
+The first line used the keyword `var` instead of the more familiar `let`, but that's just to signal that the variable we're introducing is mutable. Syntactically it acts just like a variant spelling of `let`. Also we access the contents of the variable in the same way, with just `x`. Whereas with the explicit reference cells, we have to say `get x`. There we can "see" the reference cell and have to explicitly "look inside it" to get at its contents. That's like seeing the "new game" button and other controls during the normal use of the video game. In the third line of the implicit mutation code, we have the "magic gesture", `x := 1`, which does something you couldn't do in the code without mutation. This is like bringing up the "new game" display by the magic elbow-and-stomping gesture, which doesn't work in real life. This lets us achieve the same effect that we did in the explicit code using `put 1 into x`, but without us needing to (or being able to) explicitly inspect or manipulate the reference cell itself.
 
 Turning to continuations, so far we've seen how to explicitly manipulate them, as in:
 
@@ -52,7 +52,7 @@ The next issue is whether the continuations are _delimited_ or not. In [[our dis
     | initial inside code        |
     | shift k ( ... )            |
     | remaining inside code      |
-    +---end----------------------+
+    +----------------------------+
     remaining outside code
 
 Really in the implementation of this there are _two_ continuations or snapshots being tracked. There's the potentially skipped code, represented by `remaining inside code` above; and there's also the continuation/snapshot that we resume with if we do skip that code, represented by `remaining outside code`. But only the first of these gets bound to a variable, `k` in the above diagram. What happens in this diagram is that `initial outside code` runs, then `initial inside code` runs, then `remaining inside code` is distilled into a function and bound to the variable `k`, then we run the `( ... )` code with `k` so bound. If that `( ... )` code invokes `k` by applying it to an argument, then `remaining inside code` is run as though the supplied argument were what the `shift k ( ... )` bit evaluated to. If the `( ... )` code doesn't invoke `k`, but just ends with a normal result like `10`, then the `remaining inside code` is skipped and we resume execution with the outside, implicitly snapshotted code `remaining outside code`.
@@ -92,27 +92,30 @@ or:
                     (shift k
                       (if (eqv? x 1) (k 10) 20))
                     100)))])
-      (+ (foo 1) 1000))
+      (+ (foo 2) 1000))
 
 That shows you how `abort` can be expressed in terms of `shift`. Rewriting the Scheme code into a more OCaml-ish syntax, it might look something like this:
 
     let foo x = reset (shift k -> if x = 1 then k 10 else 20) + 100) in
-    foo 1 + 1000
+    foo 2 + 1000
 
 However, OCaml doesn't have any continuation operators in its standard deployment. If you [[installed Oleg's delimcc library|/rosetta3/#delimcc]], you can use the previous code after first doing this:
 
     # #directory "+../delimcc";;
     # #load "delimcc.cma";;
-    # let prompt = ref None;;
-    # let reset body = let p = Delimcc.new_prompt () in begin prompt := Some p; Delimcc.push_prompt p body end;;
-    # let shift fun_k = match !prompt with None -> failwith "shift must be inside reset" | Some p -> Delimcc.shift p fun_k;;
+    # let reset_label = ref None;;
+    # let reset body = let p = Delimcc.new_prompt () in
+      reset_label := Some p; let res = Delimcc.push_prompt p body in reset_label := None; res;;
+    # let shift fun_k = match !reset_label with
+      | None -> failwith "shift must be inside reset"
+      | Some p -> Delimcc.shift p fun_k;;
 
 Also, the previous code has to be massaged a bit to have the right syntax. What you really need to write is:
 
     let foo x = reset (fun () -> shift (fun k -> if x = 1 then k 10 else 20) + 100) in
-    foo 1 + 1000
+    foo 2 + 1000
 
-That will return `1110` just like the Scheme code does. If you said `... foo 2 + 1000`, you'll instead get `1020`.
+That will return `1020` just like the Scheme code does. If you said `... foo 1 + 1000`, you'll instead get `1110`.
 
 That was all *delimited* continuation operators. There's also the **undelimited continuation operators**, which historically were developed first. Here you don't see the same kind of variety that you do with the delimited continuation operators. Essentially, there is just one full-strength undelimited continuation operator. But there are several different syntactic forms for working with it. (Also, a language might provide handicapped continuation operators alongside, or instead of, the full-strength one. Some loser languages don't even do that much.) The historically best-known of these is expressed in Scheme as `call-with-current-continuation`, or `call/cc` for short. But we think it's a bit easier to instead use the variant `let/cc`. The following code is equivalent, and shows how these two forms relate to each other:
 
@@ -130,7 +133,7 @@ returns `101`, whereas:
 
 only returns `1`. It is possible to duplicate the behavior of `let/cc` using `reset`/`shift`, but you have to structure your code in certain ways to do it. In order to duplicate the behavior of `reset`/`shift` using `let/cc`, you need to also make use of a mutable reference cell. So in that sense delimited continuations are more powerful and undelimited continuations are sort-of a special case.
 
-(In the OCaml code above for using delimited continuations, there is a mutable reference cell, but this is just for convenience. Oleg's library is designed for use with _multiple_ reset blocks having different labels, then when you invoke `shift` you have to specify which labeled reset block you want to potentially skip the rest of. We haven't introduced that complexity into our discussion, so for convenience we worked around it in showing you how to use `reset` and `shift` in OCaml. And the mutable reference cell was only playing the role of enabling us to work around the need to explicitly specify the `reset` block's label.)
+(In the OCaml code above for using delimited continuations, there is a mutable reference cell `reset_label`, but this is just for convenience. Oleg's library is designed for use with _multiple_ reset blocks having different labels, then when you invoke `shift` you have to specify which labeled reset block you want to potentially skip the rest of. We haven't introduced that complexity into our discussion, so for convenience we worked around it in showing you how to use `reset` and `shift` in OCaml. And the mutable reference cell was only playing the role of enabling us to work around the need to explicitly specify the `reset` block's label.)
 
 Here are some examples of using these different continuation operators. The continuation that gets bound to `k` will be in bold. I'll use an OCaml-ish syntax because that's easiest to read, but these examples don't work as-is in OCaml. The `reset`/`shift` examples need to be massaged into the form displayed above for OCaml; and the `let/cc` examples don't work in OCaml because that's not provided. Alternatively, you could massage all of these into Scheme syntax. You shouldn't find that hard.
 
@@ -138,16 +141,18 @@ Here are some examples of using these different continuation operators. The cont
     This evaluates to `111`. Nothing exotic happens here.
 
 2.  <pre><b>100 + </b>let/cc k (10 + k 1)</pre>
-    This evaluates to `101`; `(+ 100 (let/cc k (+ 10 (k 1))))` is the same as `(reset (+ 100 (shift k (k 1))))`.
+    This evaluates to `101`. See also example 11, below.
 
 3.  <pre><b>let p = </b>let/cc k (1,k) <b>in
     let y = snd p (2, ident) in
     (fst p, y)</b></pre>
-    In the second line, we extract the continuation function (the bold part of the previous code) from the pair `p` and apply it to the argument `(2, ident)`. That results in the following code being run:
+    In the first line, we bind the continuation function (the bold code) to `k` and then bind the pair of `1` and that function to the variable `p`.
+    In the second line, we extract the continuation function from the pair `p` and apply it to the argument `(2, ident)`. That results in the following code being run:
     <pre><b>let p = </b>(2, ident) <b>in
     let y = snd p (2, ident) in
     (fst p, y)</b></pre>
     which in turn results in the nested pair `(2, (2, ident))`.
+    Notice how the first time through, when `p`'s second element is a continuation, applying it to an argument is a bit like time-travel? The metaphysically impossible kind of time-travel, where you can change what happened. The second time through, `p` gets bound to a different pair, whose second element is the ordinary `ident` function.
 
 4.  <pre><b>1000 + (100 + </b>abort 11<b>)</b></pre>
     Here the box is implicit, understood to be the rest of the code. The result is just the abort value `11`, because the bold code is skipped.
@@ -156,17 +161,25 @@ Here are some examples of using these different continuation operators. The cont
     Here the box or delimiter is explicitly specified. The bold code is skipped, but the outside code `1000 + < >` is still executed, so we get `1011`.
 
 6.  <pre>1000 + reset <b>(100 + </b>shift k (10 + 1)<b>)</b></pre>
-    Equivalent to preceding; results in `1011`.
+    Equivalent to preceding. We bind the bold code to `k` but then never apply `k`, so the value `10 + 1` is supplied directly to the outside code `1000 + < >`, resulting in `1011`.
 
 7.  <pre>1000 + reset <b>(100 + </b>shift k (k (10 + 1))<b>)</b></pre>
     Here we do invoke the captured continuation, so what gets passed to the outside code `1000 + < >` is `k (10 + 1)`, that is, `(100 + (10 + 1))`. Result is `1111`.
+    In general, if the last thing that happens inside a `shift k ( ... )` body is that `k` is applied to an argument, then we do continue running the bold code between `shift k ( ... )` and the edge of the `reset` box.
 
 8.  <pre>1000 + reset <b>(100 + </b>shift k (10 + k 1)<b>)</b></pre>
-    This also results in `1111`, but via a different path than the preceding. First, note that `k` is bound to `100 + < >`. So `k 1` is `101`. Then `10 + k 1` is `10 + 101`. Then we exit the body of `shift k ( ... )`, without invoking `k` again, so we don't anymore add `100`. Thus we pass `10 + 101` to the outside code `1000 + < >`. So the result is `1000 + (10 + 101)` or `1111`. (Whereas in the preceding example, the result was `1000 + (100 + 11)`. The order in which the operations are performed is different. If we used a non-commutative operation instead of `+`, the results of these two examples would be different from each other.)
+    This also results in `1111`, but via a different path than the preceding. First, note that `k` is bound to `100 + < >`. So `k 1` is `101`. Then `10 + k 1` is `10 + 101`. Then we exit the body of `shift k ( ... )`, without invoking `k` again, so we don't add `100` any more times. Thus we pass `10 + 101` to the outside code `1000 + < >`. So the result is `1000 + (10 + 101)` or `1111`. (Whereas in the preceding example, the result was `1000 + (100 + 11)`. The order in which the operations are performed is different. If we used a non-commutative operation instead of `+`, the results of these two examples would be different from each other.)
 
 9.  <pre>1000 + reset <b>(100 + </b>shift k (k)<b>)</b> 1</pre>
-    Here `k` is bound to `100 + < >`. That's what's returned by the `shift k ( ... )` block, and since `k` isn't invoked (applied) when doing so, the rest of the bold `reset` block is skipped (for now). So we resume the outside code `1000 + < > 1`, with what fills the gap `< >` being the function that was bound to `k`. Thus this is equivalent to `1000 + (fun x -> 100 + x) 1` or `1000 + 101` or `1101`.
+    Here `k` is bound to `100 + < >`. That function `k` is what's returned by the `shift k ( ... )` block, and since `k` isn't invoked (applied) when doing so, the rest of the bold `reset` block is skipped (for now). So we resume the outside code `1000 + < > 1`, with what fills the gap `< >` being the function that was bound to `k`. Thus this is equivalent to `1000 + (fun x -> 100 + x) 1` or `1000 + 101` or `1101`.
 
 10. <pre>1000 + reset <b>(100 + </b>shift k (k (k 1))<b>)</b></pre>
-    Here `k` is bound to `100 + < >`. Thus `k 1` is `101`. Now there are two ways to think about what happens next. (Both are valid.) One way to think is that since the `shift` block ends with an additional outermost application of `k`, we continue through the bold code with the value `k 1` or `101`. Thus we get `100 + 101`, and then we continue with the outermost code `1000 + < >`, getting `1000 + (100 + 101)`, or `1201`. The other way to think is that since `k` is `100 + < >`, and `k 1` is `101`, then `k (k 1)` is `201`. Now we leave the `shift` block *without* executing the bold code a third time (we've already taken account of the two applications of `k`), resuming with the outside code `1000 + < >`, thereby getting `1000 + 201` as before.
+    Here `k` is bound to `100 + < >`. Thus `k 1` is `101`. Now there are two ways to think about what happens next. (Both are valid.) One way to think is that since the `shift` block ends with an additional outermost application of `k`, then as described in example 7 above, we continue through the bold code with the value `k 1` or `101`. Thus we get `100 + 101`, and then we continue with the outermost code `1000 + < >`, getting `1000 + (100 + 101)`, or `1201`. The other way to think is that since `k` is `100 + < >`, and `k 1` is `101`, then `k (k 1)` is `201`. Now we leave the `shift` block *without* executing the bold code a third time (we've already taken account of the two applications of `k`), resuming with the outside code `1000 + < >`, thereby getting `1000 + 201` as before.
+
+11. Here's a comparison of `let/cc` to `shift`. Recall example 2 above was:
+    <pre><b>100 + </b>let/cc k (10 + k 1)</pre>
+    which evaluated to `101`. The parallel code where we instead capture the continuation using `shift k ( ... )` would look like this:
+    <pre>reset <b>(100 + </b>shift k (10 + k 1)<b>)</b></pre>
+    But this evaluates differently. In the `let/cc` example, `k` is bound to the rest of the computation *including its termination*, so after executing `k 1` we never come back and finish with `10 + < >`. A `let/cc`-bound `k` never returns to the context where it was invoked. Whereas the `shift`-bound `k` only includes up to the edge of the `reset` box --- here, the rest of the computation, but *not including* its termination. So after `k 1`, if there is still code inside the body of `shift`, as there is here, we continue executing it. Thus the `shift` code evaluates to `111` not to `101`.
 
+    Thus code using `let/cc` can't be *straightforwardly* translated into code using `shift`. It can be translated, but the algorithm will be more complex.