3

In class we talked about two functions in racket i-e letcc and throw. The instructor said that let/cc has something to do with calling with the current continuation and throw just applies a to b e.g.

(throw a b)

I haven't been able to find much about these online. I did find a question asked for letcc on stackoverflow but I have not been able to understand the functioning of letcc from it completely. Could someone explain these two in simple words along with a simple example?

Edit1: Also in my practice mid exam we are given two question related to it.

For each of the following expressions that contain uses of let/cc, what is the value of each expression?

(let/cc k (throw (throw k 5) 6))
(let/cc k (throw k ((( lambda (x) x) k) (∗ 5 5))))

The answers to these are 5 and 25 respectively. I just wanna understand the two concepts so that I can work with questions like these in my midterm exam.

Community
  • 1
  • 1
danny
  • 400
  • 2
  • 18
  • Maybe [this implementation](https://github.com/dwayne/eopl3/commit/0f5125047e9e3031d6272c4d16270287daf66f2e) of `letcc` and `throw` will help you understand how they are supposed to work. – Dwayne Crooks Aug 10 '22 at 12:26

1 Answers1

4

Let's look at let/cc first.

The expression (let/cc k e) will 1) capture the current continuation (represented as a function) then 2) bind the variable k to the captured continuation and finally 3) evaluate the expression e.

A few examples are in order.

If during evaluation of the expression e, the captured contination k is not called, then the value of the let/cc expression is simply the value(s) to which the expression e evaluated to.

> (+ 10 (let/cc k 32)) 
42

If on the other hand k is called with a value v then the value of the entire let\cc expression becomes v.

> (+ 10 (let/cc k (+ 1 (k 2)))) 
12

Notice that the part (+ _) around the call (k 2) is skipped. The value is return to the continuation of (let/cc ...) immediately.

The most common use of let/cc is to mimic the control struct return known from many statement based languages. Here is the classical is-it-a-leap-year problem:

(define (divisible-by? y k)
  (zero? (remainder y k)))

(define (leap-year? y)
  (let/cc return
    (when (not (divisible-by? y 4))
      (return #f))
    (when (not (divisible-by? y 100))
      (return #t))
    (when (not (divisible-by? y 400))
      (return #f))
    #t))

(for/list ([y (in-range 1898 1906)])
  (list y (leap-year? y)))

Now what about throw ? That depends on which throw we are talking about. Is it the one from misc1/throw ?

Or perhaps the one from Might's article? http://matt.might.net/articles/programming-with-continuations--exceptions-backtracking-search-threads-generators-coroutines/

Or perhaps you are using the definition

(define (throw k v)
   (k v))

If the latter, then you can replace (k v) in my examples with (throw k v).

UPDATE

Note that the continuation bound to k can be used more than once - it can also be used outside the let/cc expression. Consider this example:

(define n 0)
(let ([K (let/cc k k)])
  (when (< n 10)
    (displayln n)
    (set! n (+ n 1))
    (K K)))

Can you figure out what it does without running it?

Here is a "step by step" evaluation of the nested let/cc example.

(let/cc k0
  ((let/cc k1
     (k0 (sub1 (let/cc k2
                 (k1 k2)))))
   1))

(let/cc k0
  (k2 1))

(let/cc k0
 ((let/cc k1
     (k0 (sub1 1)))
   1))

(let/cc k0
 ((let/cc k1
     (k0 0))
   1))

0
soegaard
  • 30,661
  • 4
  • 57
  • 106
  • I think throw is really just the last definition that you mentioned i-e it is simply applying k to v. I thought there was more to it. Thank you for the elaborate answer. Really appreciate it. – danny Oct 19 '16 at 22:10
  • How would the execution proceed if i have nested let/cc? e.g `(let/cc k0 (( let/cc k1 (k0 (sub1 (let/cc k2 (k1 k2))))) 1))` – danny Oct 19 '16 at 23:39
  • Try it out. If you find an example that surprises you. Post a new question. – soegaard Oct 19 '16 at 23:41
  • Regarding the update: I think the printed values would be 0-10. So, our initial K will have n set to 0 and every time we update the value of n and run (K K) our continuation will have the value updated to n=1, n=2 and so on. Hence every time our "when" block will have the updated value of n from the continuation – danny Oct 20 '16 at 00:03
  • You are right. I have made an attempt to explain the nested example. – soegaard Oct 20 '16 at 00:06
  • The solution for nested let/cc makes sense except for the 2nd step in the computation `(let/cc k0 (k2 1)) `. How did you deduce this? – danny Oct 20 '16 at 00:22
  • Here is how to go from step 1 to step 2: First we need to look at `(let/cc k1 (k0 (sub1 (let/cc k2 (k1 k2)))))` It binds `k1` and then evaluates `(k0 (sub1 (let/cc k2 (k1 k2)))))`. This is a function call, so the arguments are evaluated first before `k0` is called. The argument is `(sub1 (let/cc k2 (k1 k2)))))`. This is another function call, so the argument `(let/cc k2 (k1 k2)))` is evaluated. This binds `k2` and then `(k1 k2)` is evaluated. Since call `k1` and `k1` was the continuation of the first expression, so the value of our very first expression `(let/cc k1 ...)` is `k2`. – soegaard Oct 21 '16 at 11:45