简体   繁体   中英

Simplest example of backwards continuations in Scheme without explicit mutation

I've written a small Scheme interpreter in C#, and realised that the way I had implemented it, it was very easy to add support for proper continuations.

So I added them... but want to "prove" that they way that I've added them is correct.

My Scheme interpreter however has no support for "mutating" state - everything is immutable.

So it was pretty easy to write a unit test to expose "upwards" continuations:

AssertEqual(Eval("(call/cc (lambda (k) (+ 56 (k 3))))"), 3);

However, I also want to write a unit test that demonstrates that if the continuation "escapes" then that still works too:

AssertEqual(Eval("(call/cc (lambda (k) k))", <some continuation>);

But of course, the above would just test that "I got a continuation"... not that it's actually a valid continuation.

All of the examples I can find, however, always end up using "set!" to demonstrate the escaped continuation.

What's the simplest Scheme example that demonstrates proper support for backwards continuations without relying on mutation?

Are backwards continuations any use without mutation? I am beginning to suspect that they are not, because you could only use it to execute the exact same calculation again... which is meaningless if there are no side-effects. Is this why Haskell does not have continuations?

I don't know if this is the simplest, but here's an example of using backwards continuations without any call to set! or similar:

(apply
  (lambda (k i) (if (> i 5) i (k (list k (* 2 i)))))
  (call/cc (lambda (k) (list k 1))))

This should evaluate to 8 .

Slightly more interesting is:

(apply
  (lambda (k i n) (if (= i 0) n (k (list k (- i 1) (* i n)))))
  (call/cc (lambda (k) (list k 6 1))))

which computes 6! (that is, it should evaluate to 720 ).

You can even do the same thing with let* :

(let* ((ka (call/cc (lambda (k) `(,k 1)))) (k (car ka)) (a (cadr ka)))
      (if (< a 5) (k `(,k ,(* 2 a))) a))

(Man, stackoverflow's syntax highlighting fails massively on scheme.)

我认为你是对的 - 没有突变,向后延续不会做任何前进延续不能做到的事情。

Functional Threads:

You can use a recursive loop to update state without mutation. including the state of the next continuation to be called. Now this is more complicated than the other examples given, but all you really need is the thread-1 and main loop. The other thread and "update" function are there to show that continuations can be used for more than a trivial example. Additionally, for this example to work you need an implementation with the named let. This can be translated into an equivalent form made with define statements.

Example:

(let* ((update (lambda (data) data))                ;is identity to keep simple for example
       (thread-1                                    
         (lambda (cc)                               ;cc is the calling continuation
           (let loop ((cc cc)(state 0))
             (printf "--doing stuff       state:~A~N" state)
             (loop (call/cc cc)(+ state 1)))))      ;this is where the exit hapens
       (thread-2
         (lambda (data)                             ;returns the procedure to be used as 
           (lambda (cc)                             ;thread with data bound
             (let loop ((cc cc)(data data)(state 0))
               (printf "--doing other stuff state:~A~N" state)
               (loop (call/cc cc)(update data)(+ state 1)))))))
  (let main ((cur thread-1)(idle (thread-2 '()))(state 0))
    (printf "doing main stuff    state:~A~N" state)
    (if (< state 6)
        (main (call/cc idle) cur (+ state 1)))))

Which outputs

doing main stuff    state:0
--doing other stuff state:0
doing main stuff    state:1
--doing stuff       state:0
doing main stuff    state:2
--doing other stuff state:1
doing main stuff    state:3
--doing stuff       state:1
doing main stuff    state:4
--doing other stuff state:2
doing main stuff    state:5
--doing stuff       state:2
doing main stuff    state:6

Here's the best I've come up with:

AssertEqual(Eval("((call/cc (lambda (k) k)) (lambda (x) 5))", 5);

Not amazing, but it is a backwards continuation which I then "call" with the actual function I wish to invoke, a function that returns the number 5.

Ah and I've also come up with this as a good unit test case:

AssertEqual(Eval("((call/cc call/cc) (lambda (x) 5))", 5);

I agree with Jacob B - I don't think it's that useful without mutable state... but would be still be interested in a counter-example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM