[comp.lang.scheme] C->Scheme mappings

joemu@mentorg.com (Joe Mueller @ APD x1367) (05/29/91)

I'm trying to map the various C control statements into
Scheme. The translation of break, continue, and return
are giving me trouble. (I am not a scheme programmer so
an obvious answer may not be obvious to me)

I suspect I need to do something with
call-with-current-continuation but I don't see exactly how
to do it. Anyone care to help?


Joe Mueller
Joe_Mueller@mentorg.com
...!uunet!mntgfx!joemu

gyro@cymbal.reasoning.COM (Scott Layson Burson) (05/31/91)

   Date: 28 May 91 19:18:34 GMT
   From: "Joe Mueller @ APD x1367" <joemu@mentorg.com>

   I'm trying to map the various C control statements into
   Scheme. The translation of break, continue, and return
   are giving me trouble. (I am not a scheme programmer so
   an obvious answer may not be obvious to me)

   I suspect I need to do something with
   call-with-current-continuation but I don't see exactly how
   to do it. Anyone care to help?

You don't need call-with-current-continuation.

`return' is implicit in Scheme: a lambda body always returns the value of its
last form.  So you don't have to do anything special with it; e.g.

   int foo() { return 3; }
  -->
   (define (foo) 3)

The way `break' and `continue' work is tied up in the way iteration is modelled
using tail-recursion.  For instance

   for (i = 0; i < 10; ++i) {
        if (p1(i)) continue;
        /* ... <1> */
        if (p2(i)) break;
        /* ... <2> */
   }

 -->

   (letrec (((loop i)
               (if (not (< i 10)) #f
                 (if (p1 i) (loop (+ 1 i)))  ; `continue'
                 ;; ... <1>
                 (if (p2 i) #f   ; `break' value necessary
                   (begin
                      ;; ... <2>
                      (loop (+ 1 i)))))))
      (loop 0))

(Apologies if I screwed up the syntax -- I haven't written much Scheme lately.)

Here you see that the `continue' has turned into `(loop (+ 1 i))', which
invokes the next cycle of the loop, and `break' has turned into `#f' (it
doesn't matter what the value is, but there has to be one).  That is, `break'
amounts to simply not invoking the next iteration.

A bit mind-bending, eh?

-- Scott

hieb@heston.cs.indiana.edu (Robert Hieb) (05/31/91)

gyro@cymbal.reasoning.COM (Scott Layson Burson) writes:


>   Date: 28 May 91 19:18:34 GMT
>   From: "Joe Mueller @ APD x1367" <joemu@mentorg.com>

>   I'm trying to map the various C control statements into
>   Scheme. The translation of break, continue, and return
>   are giving me trouble.

>You don't need call-with-current-continuation.

>`return' is implicit in Scheme: a lambda body always returns the value of its
>last form.  So you don't have to do anything special with it...

This is true only if the "return" is in tail position with respect to
the procedure body, which is not in general true in C code.  Such code
can often be rewritten to put all return statements in tail position,
but that may not be the sort of transformation desired, and is not
always reasonable.  To get a fully general return mechanism "call/cc"
IS required; one must wrap a call to "call/cc" around each procedure
body that has an explicit return in nontail position.  One can then
invoke the continuation to return.

>The way `break' and `continue' work is tied up in the way iteration is modelled
>using tail-recursion.

"continue" statements cannot be turned into recursive function
invocations, nor can "break" statements be removed, unless they are in
tail position with respect to the loop body.  One may be able to
rewrite code to obey this constraint, but it is not always trivial to
do so, especially if automatic or literal translations are desired.
"break" can be gotten easily by wrapping the recursion in a "call/cc"
invocation.  "continue" is more difficult.  For a loop of the form
   (while test body)
the following would work:

   (call/cc
      (lambda (break)
         (let loop ()
            (call/cc
               (lambda (continue)
                  (if <test>
                      <body>
                      (break #f))))
            (loop))))

To limit the number of continuations captured to two (instead of two
plus one for each loop repeat) one can also write:

   (call/cc
      (lambda (break)
         (let ((continue (call/cc (lambda (k) k))))
            (let loop () (if <test> (begin <body> (loop)))))))

To make the above work, "continues" must look like
    (continue continue)
rather than
    (continue <anything>)

Or limit the number of continuations captured to one, at the expense
of an extra test each time you use one:

    (let ((loop-control (call/cc (lambda (k) k))))
       (if loop-control
           (let loop () (if <test> (begin <body> (loop))))))

Now "continue" statements look like
    (loop-control loop-control)
and  "break" statments look like
    (loop-control #f)

It is true that one rarely needs to use continuations for loop control
or procedure exit in Scheme---proper use of tail-recursive procedures
is usually adequate.  Using continuations unnecessarily in Scheme is
just as bad as using "goto" (or "break", "continue", or "return")
unecessarily in C.

quale@saavik.cs.wisc.edu (Douglas E. Quale) (06/01/91)

In article <1991May31.093235.25653@news.cs.indiana.edu> hieb@heston.cs.indiana.edu (Robert Hieb) writes:
>gyro@cymbal.reasoning.COM (Scott Layson Burson) writes:
>
>
>>   Date: 28 May 91 19:18:34 GMT
>>   From: "Joe Mueller @ APD x1367" <joemu@mentorg.com>
>
>>   I'm trying to map the various C control statements into
>>   Scheme. The translation of break, continue, and return
>>   are giving me trouble.
>
>>You don't need call-with-current-continuation.
>
>>`return' is implicit in Scheme: a lambda body always returns the value of its
>>last form.  So you don't have to do anything special with it...
>
>This is true only if the "return" is in tail position with respect to
>the procedure body, which is not in general true in C code.  Such code
>can often be rewritten to put all return statements in tail position,
>but that may not be the sort of transformation desired, and is not
>always reasonable.  To get a fully general return mechanism "call/cc"
>IS required; one must wrap a call to "call/cc" around each procedure
>body that has an explicit return in nontail position.  One can then
>invoke the continuation to return.
>

Actually regardless of where the return occurs, it is always possible to
rewrite the code using no more than conditionals and procedure calls. 
A return is no more powerful than a goto, and gotos can be translated into
procedure calls.  The result isn't always very pretty.

See the first published paper on lisp, by John McCarthy in CACM circa 1961....
(I've given up waiting for part 2!)

-- Doug Quale
quale@saavik.cs.wisc.edu

gyro@cymbal.reasoning.COM (Scott Layson Burson) (06/02/91)

   Date: Fri, 31 May 91 09:32:15 EST
   From: Robert Hieb <hieb@heston.cs.indiana.edu>

   gyro@cymbal.reasoning.COM (Scott Layson Burson) writes:

   >   Date: 28 May 91 19:18:34 GMT
   >   From: "Joe Mueller @ APD x1367" <joemu@mentorg.com>

   >   I'm trying to map the various C control statements into
   >   Scheme. The translation of break, continue, and return
   >   are giving me trouble.

   >You don't need call-with-current-continuation.

   >`return' is implicit in Scheme: a lambda body always returns the value of its
   >last form.  So you don't have to do anything special with it...

   This is true only if the "return" is in tail position with respect to
   the procedure body, which is not in general true in C code.  Such code
   can often be rewritten to put all return statements in tail position,
   but that may not be the sort of transformation desired, and is not
   always reasonable.  To get a fully general return mechanism "call/cc"
   IS required; one must wrap a call to "call/cc" around each procedure
   body that has an explicit return in nontail position.  One can then
   invoke the continuation to return.

It is *always* possible to transform the C program into the appropriate
form.  Therefore CALL/CC is *not* *required* for a "fully general"
return mechanism (in the C sense).  It is not until you get to
setjmp/longjmp that CALL/CC becomes necessary.

Whether the result is "pretty" is another question, but beauty is in the
eye of the beholder.  I think it is quite a worthwhile exercise for
anyone to understand how control constructs from traditional languages
get translated into Scheme by means of these transformations, and
without use of CALL/CC.  I would go so far as to say that such an
understanding is a prerequisite to developing a true Scheme style.

This is the spirit in which I replied to Joe Mueller's question.  Yes,
you can do these things with CALL/CC, but you'll miss the chance to
really understand what Scheme is all about.

-- Scott
Gyro@Reasoning.COM