[comp.object] Continuations

shap@delrey.sgi.com (Jonathan Shapiro) (11/28/89)

I do not believe that it is feasible to add continuations to C++ for
any number of reasons, but I would be interested to hear the reactions
in this community regarding their utility in object-oriented programming.

For those of you who are not already conversant with continuations,
the idea comes from Scheme (though it has earlier antecedents), and
looks like this:

   (call-with-current-continuation
     (lambda (continuation)
       (body...)))

the call-with-current-continuation form potentially returns multiple
times, because the continuation can be saved to a global variable.  A
subsequent invocation can be done with:

   (continuation value-producing-form)

which causes a return from the call-with-current-continuation with
that value.

Logically, the continuation captures the future state of the program
at the point at which it was captured.  That is, it captures a pc, a
set of registers, and a stack.  Note that this implies garbage
collection of stack frames, because a live frame in an enclosing
context can be shared between a continuation and subsequent code.

Continuations have seen use as error handlers, because they look like
function invocations.  One can set up an exception handling routine,
capture it with a continuation, and use the continuation as a
"longjump" type mechanism to get out of a bad state.

Continuations are different from longjump in that they capture the
stack as well as the registers.

A detailed description can be found in the Revised Revised... Report
on scheme, which can be obtained via anonymous FTP from
zurich.ai.mit.edu.

Jonathan Shapiro
Silicon Graphics, Inc.

davidm@uunet.UU.NET (David S. Masterson) (11/28/89)

In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:

   I do not believe that it is feasible to add continuations to C++ for
   any number of reasons, but I would be interested to hear the reactions
   in this community regarding their utility in object-oriented programming.

Hmmm.  Something in this caught my interest, but I'm not quite sure what.
Taking a purely object-oriented stab at it, might the idea of continuations be
implemented as merely messaging a static data area?  Is there more to it?
--
===================================================================
David Masterson					Consilium, Inc.
uunet!cimshop!davidm				Mt. View, CA  94043
===================================================================
"If someone thinks they know what I said, then I didn't say it!"

dlw@odi.com (Dan Weinreb) (11/29/89)

In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:


   I do not believe that it is feasible to add continuations to C++ for
   any number of reasons, but I would be interested to hear the reactions
   in this community regarding their utility in object-oriented programming.

They are just as useful in object-oriented programming as in straight
Lisp or Scheme.  However, continuations in the Scheme style are only
useful if full support is provided for lexical scoping.  C and C++
have no lexical scoping whatsoever.  So I agree that it is not
feasible to add them to C++.  Even lexical scoping alone, with or
without continuations, would be a great benefit, but I'm sure I'll
never see it in C or C++.

peterd@cs.washington.edu (Peter C. Damron) (11/29/89)

In article <1989Nov28.183816.15252@odi.com> dlw@odi.com writes:
>In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
>
>   I do not believe that it is feasible to add continuations to C++ for
>   any number of reasons, but I would be interested to hear the reactions
>   in this community regarding their utility in object-oriented programming.
>
>They are just as useful in object-oriented programming as in straight
>Lisp or Scheme.

How can you make this claim?

>However, continuations in the Scheme style are only
>useful if full support is provided for lexical scoping.  C and C++
>have no lexical scoping whatsoever.

I just had to reply when I saw this.  C and C++ are definitely "lexically
scoped" (I would prefer to call it statically scoped).

Peter.

---------------
Peter C. Damron
Dept. of Computer Science, FR-35
University of Washington
Seattle, WA  98195

peterd@cs.washington.edu
{ucbvax,decvax,etc.}!uw-beaver!uw-june!peterd

gateley@m2.csc.ti.com (John Gateley) (11/29/89)

In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
>I do not believe that it is feasible to add continuations to C++ for
>any number of reasons, but I would be interested to hear the reactions
>in this community regarding their utility in object-oriented programming.

I don't see why it would not be feasible, C++ has for loops and other
control structures, why not continuations as well?

As for object oriented continuations: A continuation is a technique for
modelling/creating flow of control. In pure object oriented systems, this
is done by message passing. Thus, a OO continuation would probably be something
which modelled a process: like a coroutine object, which you send messages
to (like resume). This is a half baked idea, but maybe worthwhile.

John
gateley@m2.csc.ti.com

grover%brahmand@Sun.COM (Vinod Grover) (11/29/89)

In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
>I do not believe that it is feasible to add continuations to C++ for
>any number of reasons, but I would be interested to hear the reactions
>in this community regarding their utility in object-oriented programming.
>

I do not know if you are talking about first-class continuations or not, but
in languages which are stack-based first-class continuations are not trivial
to add. (By stack-based I mean that the procedure entry exit sequance
behaves in a stack-like fashion) In C the idea of setjmp/longjump comes as
close to continuations as one can get. Admittedly, this is not as elegant as
Scheme's notion of continuation but workable in concept.

>
>Jonathan Shapiro
>Silicon Graphics, Inc.

Vinod Grover
Sun Microsystems.

shap@delrey.sgi.com (Jonathan Shapiro) (11/29/89)

In article <CIMSHOP!DAVIDM.89Nov27231415@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
>In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
>
>   I do not believe that it is feasible to add continuations to C++ for
>   any number of reasons, but I would be interested to hear the reactions
>   in this community regarding their utility in object-oriented programming.
>
>Hmmm.  Something in this caught my interest, but I'm not quite sure what.
>Taking a purely object-oriented stab at it, might the idea of continuations be
>implemented as merely messaging a static data area?  Is there more to it?
>--
>David Masterson					Consilium, Inc.
>uunet!cimshop!davidm				Mt. View, CA  94043

I don't believe so.  What is interesting about continuations is that
they freeze an execution context into an object whose internals are
not exposed to the programmer, but which can be invoked at a later
time and passed a single value which is the value to return.  Since
the object can be saved outside of the scope in which it was defined,
the continuation can be returned from multiple times.

The reason messaging a static data area isn't enough is that you need
to preserve the stack, and portions of the stack can be shared between
the captured continuation and the main program thread.

As an example:

(if (call-with-current-continuation
     (lambda (cont)
       (set! *saved-continuation* cont)
       #f))
    (do-something-interesting))

When first run, the continuation is passed to the lambda, which saves
it and returns #f, causing the IF to fail.  At any later time, the
user can say:

(*saved-continuation* non-#f-value)

and the body of the if will be run, along with any code that ran
following the execution of the if the first time.  One could therefore
package up an error routine in a top level loop as shown above and
then implement

(define (recover-from error-type cause-info)
  (call-with-current-continuation
   (lambda (cont)
     (function-that-handles-error-type cont cause-info))))

(define (catch-math-error where-happened cause-description)
  (if (try-to-recover cause)
      (cont #t)
      (*saved-continuation-for-unrecoverable-math-error where-happened)))

And then say in the code encountering the error:

(if (recover-from 'math-error 'overflow)
    (continue-happily)
    (die-horribly))

Note that the "cont" can be invoked by any handler in the chain to get
back to the *original* context, and that if any of these pass #t to
the continuation, execution will continue appropriately.  If some
handler gives up and invokes the continuation with #f, we call
DIE-HORRIBLY, and presumably exit.

I don't know if that makes things clearer or not...

Jonathan Shapiro
Silicon Graphics, Inc.

plogan@mentor.com (Patrick Logan) (11/29/89)

Jonathan Shapiro (shap@delrey.sgi.com) is interested in the utility of
continuations in object-oriented programming.

My response is they have great utility. He mentioned an example of
defining an exception handling system using continuations. If you're
unfamiliar with continuations, consider another, unrelated example and
you may begin to realize the power of continuations.

ASIDE...
Before I continue with the example, I think it's important to point
out that continuations exist conceptually in any programming language
that provides sequential execution. For example C, Pascal, and C++
have sequential statements that can be described using the concept of
continuations. Even Prolog messes with sequential execution with the
concepts of backtracking and cut. The real *power* of continuations
comes with languages such as Scheme that provide continuations as
full-fledged objects in the language.
END OF ASIDE

My example of an object that uses continuations is a discrete event
simulator. Let's say your simulating (very simplistically**) a digital
electronic circuit. You could have an object representing a two-input
AND gate. This object has (among others) a method for setting the
value of an input. Here is some pseudo code describing that method:

  When one of the input pins is assigned a new value perform...
    (1) Assign the new value to the input pin.
    (2) Notify the discrete event simulator that the next step
        should execute two simulation time units from the current time.
        This models the delay of the real AND gate.
    (3) Assign to the output pin the result of AND'ing the input pins.

In a Scheme-based object system this might look like:

    (define-method (set-input-pin! and-gate pin-number new-value)
      ;; Step #1.
      (case pin-number
        (1 (set! input-pin-one new-value))
        (2 (set! input-pin-one new-value))
        (else (error "Invalid pin number." pin-number)))
      ;; Step #2.
      (simulator 'wait-for 2)
      ;; Step #3.
      (self 'set-output-pin! (and input-pin-one input-pin-two)))

The wait-for message to the simulator object implements step #2 above and
uses continuations. The pseudo code may be:

  When the wait-for message is sent perform...
    (1) Capture the current continuation.
    (2) Schedule it to be executed at the current time plus the value
        sent in the message.
    (3) Choose the next scheduled continuation.
    (4) Set the current time to be the time of that continuation.
    (5) Execute that continuation.

The Scheme-like code may be:

    (define-method (wait-for simulator relative-time)
      ;; Step #1.
      (call-with-current-continuation
	(lambda (k)
          ;; Step #2.
	  (scheduler 'schedule k (+ (self 'current-time) relative-time))
          (receive (next-scheduled-k new-time)  ;; Bind these two names
	      (scheduler 'next)                 ;; to the two values returned by this message. Step #3.
            (self 'set-time! new-time)          ;; Then set the new time (Step #4.)
            (next-scheduled-k #t)               ;; and execute the continuation with a meaningless argument. Step #5.

So you can see that continuations are used to schedule future events
in a neat way. They allow control to be handed over to other events
that are scheduled to occur sooner. By embedding the capturing of
continuations in the simulator object, the details are abstracted so
that the description of the model is also very clean.

** This algorithm does not correspond to Mentor Graphics' products! 8)

-- 
Patrick Logan                | ...!{decwrl,sequent,tessi}!mntgfx!plogan 
Mentor Graphics Corporation  | plogan@pdx.MENTOR.COM                    
Beaverton, Oregon            |

barmar@think.com (11/29/89)

In article <128489@sun.Eng.Sun.COM> grover@sun.UUCP (Vinod Grover) writes:
>I do not know if you are talking about first-class continuations or not, but
>in languages which are stack-based first-class continuations are not trivial
>to add. (By stack-based I mean that the procedure entry exit sequance
>behaves in a stack-like fashion)

This is a circular argument.  It's the very existence of first-class
continuations in Scheme-like languages that causes them not to be
stack-based.  If Scheme didn't have continuations then it would be
stack-based, according to your definition.  Scheme is simply the result of
adding continuations to Lisp.  Of course, when you add them you have to
redesign implementations to handle them properly.  This means that you
can't always use a linear stack to implement activation records -- you may
have to allocate activations in the heap and garbage collect them.

Upward closures also require similar support.  First-class continuations
are simply an extension of upward closures to include control information
as well as variable bindings.
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

shap@delrey.sgi.com (Jonathan Shapiro) (11/30/89)

In article <31794@news.Think.COM> barmar@think.com writes:
>... Scheme is simply the result of adding continuations to Lisp.
>...Upward closures also require similar support.  First-class continuations
>are simply an extension of upward closures to include control information
>as well as variable bindings.

I think you got it right the second time.  The major thing that Scheme
offers is first-class closures (of which functions, continuations, and
in some implementations, environments are examples).  There are also
some miscellaneous alterations, such as eliminating the namespace
distinction between functions and variables, that are a consequence of
making closures first class.

Jon

max@Neon.Stanford.EDU (Max Hailperin) (11/30/89)

In article <1989Nov28.224937.4183@mentor.com> plogan@mentor.com
 (Patrick Logan) writes:
> ...
>My example of an object that uses continuations is a discrete event
>simulator.
> ...


This is a superb example, but stops one step short of showing the real
power of the idea.  If all you are simulating is simple things like and
gates, you can just code the simulator the way Abelson and Sussman do
in their book, something like
  (simulator 'after-delay
              2
              (lambda ()
                (self 'set-output-pin! (and input-pin-one input-pin-two))))
instead of
  (simulator 'wait-for 2)
  (self 'set-output-pin! (and input-pin-one input-pin-two))

The win in doing it the latter way (where simulator's wait-for uses
call-with-current-continuation) comes if you are simulating something
higher level.  For example, I've been involved in some multiprocessor
simulations where you want to just treat the processing elements as
black boxes and simulate them by directly running the application code
on the real machine that's doing the simulating, time how long it takes,
and apply some scaling factor.  *However*, whenever a remote reference
happens, you want to trap back to the simulator, which will cause the
right sequence of events to happen to simulate the network traffic,
etc.  Meanwhile, other processors may be doing their thing.  Eventually,
the event happens that corresponds to the memory contents from the
remote reference arriving back at the processor, and you wnat to
pick up where you left off -- which may be arbitrarily deeply nested
in procedure calling.  This can trivially be done by embedding a
call-with-current-continuation in the remote-reference code, but otherwise
is a real nightmare.  I know--I've done it both ways.

The key difference between the processor and the and-gate is that the
point at which you want to delay may be buried deep in calls.  This
can happen in other, simpler situations as well, basically any time
you have a notion of "interruption" in the normal, non-trivial,
behavior of an entity.  The fact that this comes up all the time in
simulating bussiness organizations or what-not explains why Simula had
coroutining (which is what first-class continuations are being used
for in this example).

kend@tekchips.LABS.TEK.COM (Ken Dickey) (11/30/89)

In article <31794@news.Think.COM> barmar@think.com writes:
>...  It's the very existence of first-class
>continuations in Scheme-like languages that causes them not to be
>stack-based. ...

[I saw this and could not help but respond... 8^]

People interested in this discussion might want to look at R. Kent
Dybvig's thesis "Three implementation models for Scheme", U of North
Carolina at Chappel Hill, 1987.  It describes heap and *stack* based
scheme implementation techniques.  Many (most?) compiled Scheme
implementations do use (possibly segmented) stacks.  A good discussion
of implementation techniques can be found in "Implementation
Strategies for Continuations" by Clinger, Hartheimer and Ost in the
'88 ACM Lisp & FP Conference Proceedings.  

I guess the gist of the above is that you can use a stack strategy
until a continuation is captured, at which time the used part of the
stack is `captured', marked immutable, and may be shared.  If indeed
shared, then it must be copied to be reexecuted--otherwise it could
not be reexecuted multiple times.  How the capture occurs depends on
your storage recycling (gc) strategy and may involve copying to the
heap, playing with page tables, etc.  There are some technical
wrinkles, but stack strategies are certainly widely used.  

Some people now believe that stacks are no longer required (e.g.
Appel, "Garbage Collection can be Faster than Stack Allocation",
Information Processing Letters, v25 #4, 1987), but that is another
question.


-Ken Dickey		kend@mrloog.WR.TEK.COM

dlw@odi.com (Dan Weinreb) (11/30/89)

In article <9964@june.cs.washington.edu> peterd@cs.washington.edu (Peter C. Damron) writes:

   In article <1989Nov28.183816.15252@odi.com> dlw@odi.com writes:
   >In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
   >
   >   I do not believe that it is feasible to add continuations to C++ for
   >   any number of reasons, but I would be interested to hear the reactions
   >   in this community regarding their utility in object-oriented programming.
   >
   >They are just as useful in object-oriented programming as in straight
   >Lisp or Scheme.

   How can you make this claim?

Easy, I just press the keys, and...  Seriously, I have used
continuations in an object-oriented programming language (Lisp with
Flavors), for years, as have several of my former colleagues.  So I
feel pretty qualified to claim that they're useful.  Do you have any
more specific rebuttal than amazement that I could even say such a
thing?

   >However, continuations in the Scheme style are only
   >useful if full support is provided for lexical scoping.  C and C++
   >have no lexical scoping whatsoever.

   I just had to reply when I saw this.  C and C++ are definitely "lexically
   scoped" (I would prefer to call it statically scoped).

In a sense, C and C++ are lexically scoped, but only in a trivial
sense, since there are no internal procedures and no block structure.
So, you are technically right, and I should revise my statement to say
that continuations are only useful if the continuation can reference
variables of lexically enclosing blocks, and classic
"continuation-passing style" in the Sussman and Steele sense requires
further that they should be able to refer to such variables even if
the enclosing block is no longer being executed (what is traditionally
known in the Lisp/Scheme community as "supporting upward funargs").
Few languages support upward funargs; Common Lisp and Scheme are two
that do.  Many languages support downward funargs; Algol-60, Pascal,
and PL/I come to mind.  In C, you can pass a function around as an
argument, but there is no "lexical link" allowing it to reference
variables in its parent block; in fact, there are no parent blocks in C.
(It can refer to static and extern, but that's not the same thing.)

Dan Weinreb		Object Design, Inc.		dlw@odi.com

dlw@odi.com (Dan Weinreb) (11/30/89)

In article <99894@ti-csl.csc.ti.com> gateley@m2.csc.ti.com (John Gateley) writes:

   I don't see why it would not be feasible, C++ has for loops and other
   control structures, why not continuations as well?

It depends what you mean.  It would certainly be possible to invent a
definition of a language that was like C, but also had the features
needed to do continuation-style programming.  Then you'd have a
definition of a new programming language.  However, if you're talking
about getting the ANSI X3J11 committee to incorporate such features
into their next draft standard, I think you'd have a lot of trouble,
since the changes would be large and would entail various tradeoffs.

plogan@mentor.com (Patrick Logan) (11/30/89)

 >>From: grover%brahmand@Sun.COM (Vinod Grover)
 >In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
 >>I do not believe that it is feasible to add continuations to C++ for
 >>any number of reasons...
 >
 >I do not know if you are talking about first-class continuations or not, but
 >in languages which are stack-based first-class continuations are not trivial
 >to add. (By stack-based I mean that the procedure entry exit sequance
 >behaves in a stack-like fashion) In C the idea of setjmp/longjump comes as
 >close to continuations as one can get. Admittedly, this is not as elegant as
 >Scheme's notion of continuation but workable in concept.
 >
 >>
 >>Jonathan Shapiro
 >>Silicon Graphics, Inc.
 >
 >Vinod Grover
 >Sun Microsystems.

To be more accurate, longjump is not workable in concept because a
longjump can only be executed once per setjmp. A continuation in
Scheme can be captured once and executed zero or more times. Things
become more complicated when you start considering nested setjmps
and longjumps.

For example, in my last article, I gave an example of a discrete event
simulator. There is no way on Earth to implement that with setjmp and
longjump.

Essentially, setjmp/longjump implement a subset of what first-class
continuations implement. There is no way to implement first-class
continuations in C/C++ without fundamentally changing the semantics of
the language. At that point, you have to poke yourself in the eye with
a sharp object and ask yourself why do that?

Have fun.


-- 
Patrick Logan                | ...!{decwrl,sequent,tessi}!mntgfx!plogan 
Mentor Graphics Corporation  | plogan@pdx.MENTOR.COM                    
Beaverton, Oregon            |

psrc@pegasus.ATT.COM (Paul S. R. Chisholm) (11/30/89)

In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
> I do not believe that it is feasible to add continuations to C++ for
> any number of reasons . . .

In article <99894@ti-csl.csc.ti.com>, gateley@m2.csc.ti.com (John Gateley) writes:
> I don't see why it would not be feasible, C++ has for loops and other
> control structures, why not continuations as well?
>John, gateley@m2.csc.ti.com

"For loops and other control structures" just require conditional
branches.   Continuations require something for subroutine calling
more complicated than a simple stack.

Remember, many C++ implementations generate C code.  If C++ provides a
feature that can't be implemented in C, that whole method (which has
made it much easier and faster to get object-oriented programming
methods into the hands of programmers) goes down the tubes.

In article <128489@sun.Eng.Sun.COM> grover@sun.UUCP (Vinod Grover) writes:
> I do not know if you are talking about first-class continuations or not, but
> in languages which are stack-based first-class continuations are not trivial
> to add. (By stack-based I mean that the procedure entry exit sequance
> behaves in a stack-like fashion)

In article <31794@news.Think.COM>, barmar@think.com writes:
> This is a circular argument.  It's the very existence of first-class
> continuations in Scheme-like languages that causes them not to be
> stack-based.
>Barry Margolin, Thinking Machines Corp., barmar@think.com,
>{uunet,harvard}!think!barmar

So you're saying that C++ (and by implication, C) would have to abandon
their simple stacks in order to have first-class continuations?

Efficiency (that is, allowing for the straightforward implementation of
translators that produce efficient code) is one of the goals of the C++
programming languages.  Even with inline functions, a C++ program will
have lots of subroutine calls.  A simple stack helps minimize the
overhead of those calls.

BTW, C++ class iterators either return a magic pointer, which can be
trivially convinced to reference the next element, or separate iterator
classes, which set aside memory (conceptually, and *very* roughly) like
separate stack frames.  They're not as elegant as iterators in Icon,
CLU, or Smalltalk.  I realize that continuations are useful for a *lot*
more things than iteration.

(Having said all that, I've painted myself into a corner:  AT&T's C++
2.0 library includes "tasks", a set of classes for coroutines.  The
library reference manual tells how they're implemented, but I've never
read that part.  Does anyone know how well the same techniques could be
applied to continuations, first-class or otherwise?)

Paul S. R. Chisholm, AT&T Bell Laboratories
att!pegasus!psrc, psrc@pegasus.att.com, AT&T Mail !psrchisholm
I'm not speaking for the company, I'm just speaking my mind.

mjl@cs.rit.edu (11/30/89)

In article <1663@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
>
>... What is interesting about continuations is that
>they freeze an execution context into an object whose internals are
>not exposed to the programmer, but which can be invoked at a later
>time and passed a single value which is the value to return.  Since
>the object can be saved outside of the scope in which it was defined,
>the continuation can be returned from multiple times.
>
>The reason messaging a static data area isn't enough is that you need
>to preserve the stack, and portions of the stack can be shared between
>the captured continuation and the main program thread.

What is the essential difference between a continuation in Scheme and
the classic notion of a coroutine?  Coroutines have been around for
years (Conway's paper describing them came out in the '60s), and can be
found in Simula-67 (where they are used with the object metaphor to
represent the semi-independent entities in a simulation).  The
"processes" of Modula-2 are also ccoroutines. There are several
packages for C that implement "light weight processes", and these are
essentially ccoroutines (I have one available if anyone is
interested).  Finally, Stroustrup has a tasking package for C++ which
captures this concept.

This is not to deny that Scheme may have a more elegant, general,and
formal (and thus less ad-hoc) approach, but I think we have to be alert
enough to recognize essential similarities buried under surface
differences.  Have I found such a similarity here?

On a side note, aren't lexical scoping and continuations independent
concepts?  Why I couldn't define a consistent language with
continuations where the meaning of a variable depends on the dynamic
invocation sequence rather than static nesting?  I might not want to
*use* such a language (dynamic scoping makes it almost impossible to
understand a module/function in isolation) but that's an engineering
issue, not one of feasibility.

Mike Lutz
Mike Lutz	Rochester Institute of Technology, Rochester NY
UUCP:		{rutgers,cornell}!rochester!rit!mjl
INTERNET:	mjlics@ultb.isc.rit.edu

barmar@think.com (12/01/89)

In article <1419@cs.rit.edu> mjl@prague.UUCP (Michael Lutz) writes:
>What is the essential difference between a continuation in Scheme and
>the classic notion of a coroutine?

I think a coroutine can only have one saved state at a time.  Whenever you
invoke a coroutine you return from the operation that last exited the
coroutine.  With continuations you can snapshot the state any number of
times, and return to any one of them.

Coroutines also don't permit you to return to functions that have already
performed a normal return.  Continuations do.

>On a side note, aren't lexical scoping and continuations independent
>concepts?  Why I couldn't define a consistent language with
>continuations where the meaning of a variable depends on the dynamic
>invocation sequence rather than static nesting?  I might not want to
>*use* such a language (dynamic scoping makes it almost impossible to
>understand a module/function in isolation) but that's an engineering
>issue, not one of feasibility.

I think you're right.  I believe that the relationship between lexical
scoping and continuations is more philosophical than architectural.  I.e.,
lexical scoping and continuations are two aspects of a larger idea about
programming, and they work well together.  Also, continuation-based
programming can be confusing, and lexical scoping makes it easier to
understand what is going on.  You are right that dynamic scoping makes it
hard to understand modules (I was taught that it violates "referential
transparency"), and adding continuations would make it even harder.


Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

barmar@think.com (12/01/89)

In article <4286@pegasus.ATT.COM> psrc@pegasus.ATT.COM (Paul S. R. Chisholm) writes:
>Remember, many C++ implementations generate C code.  If C++ provides a
>feature that can't be implemented in C, that whole method (which has
>made it much easier and faster to get object-oriented programming
>methods into the hands of programmers) goes down the tubes.

Anything can be implemented in C (it's Turing complete, or whatever the
term is).  The C code may not look like the corresponding C++ code, but who
cares?  There are Lisp compilers that generate C code, and they have no
problem dealing with the fact that C doesn't have complex and
infinite-precision numbers or a garbage collector.  And on Symbolics Lisp
Machines, the C compiler generates Lisp code.  In both directions, there is
no rule that similar concepts must be implemented using the corresponding
features of each language; for instance, C++ activation records don't have
to map directly to C activation records.

>So you're saying that C++ (and by implication, C) would have to abandon
>their simple stacks in order to have first-class continuations?
>
>Efficiency (that is, allowing for the straightforward implementation of
>translators that produce efficient code) is one of the goals of the C++
>programming languages.  Even with inline functions, a C++ program will
>have lots of subroutine calls.  A simple stack helps minimize the
>overhead of those calls.

It's a common fallacy that continuations impose excessive overhead.  First
of all, the overhead is generally only imposed when continuations are
actually used.  A program that obeys pure stack discipline should not be
affected.  Only when the compiler sees that a continuation is being used
might it have to generate extra code to deal with it.  Other people have
pointed out the literature on "segmented stacks", which are alternatives to
heap-allocated activation records.  I'm not extremely familiar with Scheme
implementation, but implementors I've talked to have convinced me that the
availability of continuations doesn't prevent efficiency.  Many of the
languages that provide continuations and/or upward closures also provide a
garbage-collected heap, so heap-allocation of activation records (when
necessary) is the simplest, and hence frequently initial, technique used by
implementors of such languages; such implementations should not be used as
a benchmark to indict continuations in general.
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

peter@ficc.uu.net (Peter da Silva) (12/01/89)

In article <1989Nov29.230331.19595@odi.com> dlw@odi.com writes:
> It depends what you mean.  It would certainly be possible to invent a
> definition of a language that was like C, but also had the features
> needed to do continuation-style programming.

There is. It's called C. Almost.

Seriously. All you need to do is have a bit of unportable code that mucks
around in struct jmp_buf. Save the stack between here and there, do a setjmp
to mark the end of the current stack, and then longjmp back to the saved
spot. To continue, restore the saved stack and longjmp back to the mark.
It would be advisable to write this code in assembly language :->.

This would also give you the ability to add coroutines, threads, and all
sorts of other good stuff. Let's call it ++C.
-- 
`-_-' Peter da Silva <peter@ficc.uu.net> <peter@sugar.lonestar.org>.
 'U`  --------------  +1 713 274 5180.
"The basic notion underlying USENET is the flame."
	-- Chuq Von Rospach, chuq@Apple.COM 

peterd@cs.washington.edu (Peter C. Damron) (12/01/89)

In article <1989Nov29.225826.19483@odi.com> dlw@odi.com writes:
>In article <9964@june.cs.washington.edu> peterd@cs.washington.edu (Peter C. Damron) writes:
>>In article <1989Nov28.183816.15252@odi.com> dlw@odi.com writes:
>>>In article <1623@odin.SGI.COM> shap@delrey.sgi.com (Jonathan Shapiro) writes:
>>>>I do not believe that it is feasible to add continuations to C++ for
>>>>any number of reasons, but I would be interested to hear the reactions
>>>>in this community regarding their utility in object-oriented programming.
>>>
>>>They are just as useful in object-oriented programming as in straight
>>>Lisp or Scheme.
>>
>>How can you make this claim?

>Easy, I just press the keys, and...  Seriously, I have used
>continuations in an object-oriented programming language (Lisp with
>Flavors), for years, as have several of my former colleagues.  So I
>feel pretty qualified to claim that they're useful.  Do you have any
>more specific rebuttal than amazement that I could even say such a
>thing?

The problem I have with including continuations into an object oriented
language is a philisophical one.  Objects should be self-contained and
hide information.  All interactions with objects should be through
the well defined methods of the object.  Now, if you add continuations,
there is the potential to pass the continuation outside of the object, thus
creating another entry point into the object that is not a member function.
I see this as an information hiding problem for continuations in OO languages.
Perhaps you can relate why this is not a problem in CLOS.

>>>However, continuations in the Scheme style are only
>>>useful if full support is provided for lexical scoping.  C and C++
>>>have no lexical scoping whatsoever.
>>
>>I just had to reply when I saw this.  C and C++ are definitely "lexically
>>scoped" (I would prefer to call it statically scoped).

>In a sense, C and C++ are lexically scoped, but only in a trivial
>sense, since there are no internal procedures and no block structure.
>So, you are technically right, and I should revise my statement to say
>that continuations are only useful if the continuation can reference
>variables of lexically enclosing blocks, and classic
>"continuation-passing style" in the Sussman and Steele sense requires
>further that they should be able to refer to such variables even if
>the enclosing block is no longer being executed (what is traditionally
>known in the Lisp/Scheme community as "supporting upward funargs").
>Few languages support upward funargs; Common Lisp and Scheme are two
>that do.  Many languages support downward funargs; Algol-60, Pascal,
>and PL/I come to mind.  In C, you can pass a function around as an
>argument, but there is no "lexical link" allowing it to reference
>variables in its parent block; in fact, there are no parent blocks in C.
>(It can refer to static and extern, but that's not the same thing.)

Let's get this straight.

1. Lexical (static) scoping is orthogonal to both continuations and
   first-class functions.  C and C++ are fully lexically scoped langauges.
   Scoping has to do with naming -- which value does a name refer to.

2. C and C++ do not allow nested functions, but they do support block
   structure.  Thus the lexical scoping is not "trivial".

3. Continuations are orthogonal to first-class functions (e.g. languages
   that allow both upward and downward funargs) and are orthogonal to
   block structure.  It is not apparent to me that continuations are
   any less useful without block structure (though C and C++ have it anyway).
   It is also not apparent to me that continuations are any less useful
   without nested functions (though maybe more cumbersome).

4. C and C++ have first class functions.  Functions can be passed as
   parameters, returned as function values, and assigned to variables.
   The reason that C and C++ do not allow nested functions is precisely
   so that they can have first-class functions implemented on a stack
   (e.g. no closure required).  The "parent block" of a function is
   simply the global variables.

Hope this helps to clear up any mis-understandings,
Peter.

---------------
Peter C. Damron
Dept. of Computer Science, FR-35
University of Washington
Seattle, WA  98195

peterd@cs.washington.edu
{ucbvax,decvax,etc.}!uw-beaver!uw-june!peterd

chewy@apple.com (Paul Snively) (12/01/89)

In article <1419@cs.rit.edu> mjl@cs.rit.edu writes:
> What is the essential difference between a continuation in Scheme and
> the classic notion of a coroutine?  Coroutines have been around for
> years (Conway's paper describing them came out in the '60s), and can be
> found in Simula-67 (where they are used with the object metaphor to
> represent the semi-independent entities in a simulation).  The
> "processes" of Modula-2 are also ccoroutines. There are several
> packages for C that implement "light weight processes", and these are
> essentially ccoroutines (I have one available if anyone is
> interested).  Finally, Stroustrup has a tasking package for C++ which
> captures this concept.
> 
> This is not to deny that Scheme may have a more elegant, general,and
> formal (and thus less ad-hoc) approach, but I think we have to be alert
> enough to recognize essential similarities buried under surface
> differences.  Have I found such a similarity here?

No, but you've come very close.  It's trivially easy to implement 
coroutines using continuations, but they are not the same thing.

Explaining why continuations are more general than coroutines is a tad 
tricky.  George Springer and Dan Friedman have a new book out from The MIT 
Press, "Scheme and the Art of Programming," that devotes an entire chapter 
to continuations, and a section in that chapter to coroutines.  I'd advise 
looking there for a reasonably full treatment of the subject.

__________________________________________________________________________
Just because I work for Apple Computer, Inc. doesn't mean that they 
believe what I believe or vice-versa.
__________________________________________________________________________
C++ -- The language in which only friends can access your private members.
__________________________________________________________________________

jcg@iris.brown.edu (James Grandy) (12/01/89)

A question: if you fix up Smalltalk blocks so that their state is not 
stored with the creator method (that is, so that they are re-entrant, 
etc.), would they be classified as continuations?

James Grandy
(401) 862-1610                jcg@iris.brown.edu
IRIS Brown University
Box 1946, Providence, RI 02912

peter@ficc.uu.net (Peter da Silva) (12/01/89)

Another question: what distinguishes continuations from generators?
-- 
`-_-' Peter da Silva <peter@ficc.uu.net> <peter@sugar.lonestar.org>.
 'U`  --------------  +1 713 274 5180.
"The basic notion underlying USENET is the flame."
	-- Chuq Von Rospach, chuq@Apple.COM 

ttwang@polyslo.CalPoly.EDU (Thomas Wang) (12/01/89)

My nitpick with continuations is that it does not fit the model of
object programming.

Continuations saves the function context to be able to continue the
function at a later time.  The information is tied to the function
activation record.

The object programming style on the other hand suggest that important
data structures should be tied to the object, not the function.

The need for continuations should be lessened if one has some light weight
process implementations at hand.

Actually I am still wondering about how one is supposed to build a
general pupose iterator in a OO language.  One can always have
Ad Hoc iterators, but a consistent iterator interface I suspect would
be hard to do.


 -Thomas Wang (Mak-Kuro Kurosuke, come on out!  If you don't come out,
               we'll pull your eyeballs out!
                                      - as heard in Tonari No Totoro )

                                                     ttwang@polyslo.calpoly.edu

barmar@think.com (12/01/89)

In article <10008@june.cs.washington.edu> peterd@june.cs.washington.edu (Peter C. Damron) writes:
>  Now, if you add continuations,
>there is the potential to pass the continuation outside of the object, thus
>creating another entry point into the object that is not a member function.
>I see this as an information hiding problem for continuations in OO languages.
>Perhaps you can relate why this is not a problem in CLOS.

CLOS makes no attempt to implement information hiding.  (slot-value
<object> <slot-name>) may be called from anywhere, not just methods.  In
CLOS, methods are just functions that know what the types of their required
arguments are, and they are invoked through generic function dispatching.

Lisp, in general, doesn't restrict the programmer's options for
philosophical reasons.  If the programmer wants to pass a lexical closure
out of a method, that's his prerogative.
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

shap@delrey.sgi.com (Jonathan Shapiro) (12/01/89)

In article <10008@june.cs.washington.edu> peterd@june.cs.washington.edu (Peter C. Damron) writes:
>4. C and C++ have first class functions.  Functions can be passed as
>   parameters, returned as function values, and assigned to variables.

>Hope this helps to clear up any mis-understandings,
>Peter.

Actually, Peter, it creates one.  C and C++ do not have fist class
functions.  Functions cannot be passed as values.  Function pointers
can, whcih is a somewhat different thing.  To be first class, it must
be possible to treat functions as *values* rather than as *labels*.
For example, the following is a syntax error:

int foo(int);
int bar(int);

foo = bar

If functions were first class, this would be altogether legal.

Otherwise, your posting was correct.


Jonathan Shapiro
Silicon Graphics, Inc.

twl@brunix (Ted "Theodore" (W) Leung) (12/02/89)

In article <10008@june.cs.washington.edu> peterd@june.cs.washington.edu (Peter C. Damron) writes:
>4. C and C++ have first class functions.  Functions can be passed as
>   parameters, returned as function values, and assigned to variables.
>   The reason that C and C++ do not allow nested functions is precisely
>   so that they can have first-class functions implemented on a stack
>   (e.g. no closure required).  The "parent block" of a function is
>   simply the global variables.
>
It seems like it would be useful to make a distinction between "first
class" functions which require their implementation via a closure, and
those which do not.  There is a fairly standard trick in the Scheme
community, which uses closures to create objects.  The closed over
variables are used to represent the local state of the object, and the
code of the closure is a big case statement which selects the correct
method to be invoked.  It seems to me that you cannot use C "first class"
functions to create objects, because they have no closure.....

I do think that C/C++ functions are not first class because it is not
possible to create a function on the fly the way that you can in LISP
or Scheme.

Ted

--------------------------------------------------------------------
Internet/CSnet: twl@cs.brown.edu 	| Ted "Theodore" Leung
BITNET: twl@BROWNCS.BITNET		| Box 1910, Brown University
UUCP: uunet!brunix!twl			| Providence, RI 02912

allen@sbcs.sunysb.edu ( Allen Leung) (12/02/89)

In article <10008@june.cs.washington.edu>, peterd@cs.washington.edu (Peter C. Damron) writes:
> 4. C and C++ have first class functions.  Functions can be passed as
>    parameters, returned as function values, and assigned to variables.
>    The reason that C and C++ do not allow nested functions is precisely
>    so that they can have first-class functions implemented on a stack
>    (e.g. no closure required).  The "parent block" of a function is
>    simply the global variables.
> 
> Peter.
> peterd@cs.washington.edu
> {ucbvax,decvax,etc.}!uw-beaver!uw-june!peterd


I must disagree.
C and C++ definitely *don't* have first class functions.  From
a function programming view point, type alpha is first class means that
values of alpha enjoy all the privilages granted to first class types.
In terms of C this means you are supposed to be able to do things like 
this with functions:

      int(*)() foo( int y; )
      {
         int x;                  // if you can local declare int/float/* ....etc
         float r;
         char *a;
         
         int bar( int z )        // you should be able to local declare bar
         {  return y + z; }      // y is free in bar but bound in foo
       
         return bar;     
      }

--Allen
allen@sbcs.sunysb.edu

max@Neon.Stanford.EDU (Max Hailperin) (12/02/89)

In article <21989@brunix.UUCP> twl@boojum.UUCP (Ted "Theodore" (W) Leung)
writes:
>In article <10008@june.cs.washington.edu> peterd@june.cs.washington.edu (Peter
>C. Damron) writes:
>>4. C and C++ have first class functions.  
>>   ... no closure required ...
>It seems like it would be useful to make a distinction between "first
>class" functions which require their implementation via a closure, and
>those which do not.

As an asside to this discussion, it appears to me that closures can be
emulated relatively smoothely in C++.  The two key features that support
this are:
 1) The procedure-call operator can be overloaded, so that you can define
    a class of closure objects which can be called as if they were procedures.
    The constructor (lambda equivalent) can copy the closed-over variables
    into slots with the same name, so that then the procedure-call member
    function can use them.
 2) The notion of a reference type (explicit aliasing) allows the closures
    to share mutable variables that are in a common scope.

Of course, this is still a bit messy, but nothing compared with
emuating closures in C.  Also, there are other language features that
interact poorly with a higher-order style, limiting the utility.

I haven't actually used this technique (I'm lucky enough to have a
real lisp, instead of having to make C++ pretend it's one).  I'd be
very interested to hear from anyone who has.

dlw@odi.com (Dan Weinreb) (12/02/89)

In article <10008@june.cs.washington.edu> peterd@cs.washington.edu (Peter C. Damron) writes:


   The problem I have with including continuations into an object oriented
   language is a philisophical one.  Objects should be self-contained and
   hide information.  All interactions with objects should be through
   the well defined methods of the object.  Now, if you add continuations,
   there is the potential to pass the continuation outside of the object, thus
   creating another entry point into the object that is not a member function.
   I see this as an information hiding problem for continuations in OO languages.
   Perhaps you can relate why this is not a problem in CLOS.

Well, I admit that this is all rather hard to articulate.  I see
continuations as a control structure concept.  It is part of the way
you tell your computer program what to do next, how the locus of
control should move throughout the lines of code.  As such, it is
really orthogonal to the important concepts of object-oriented
programming: you can have loops in OOPS, and you can have recursion in
OOPS, so why not coroutines and continuations?

In this specific case, no, I don't think it creates a new
"entrypoint", although this statement is based on a concept of
"entrypoint" that I'm having trouble articulating.  Part of the
important point is that encapsulation is not violated, because only a
method of a class can create a continuation that continues within that
class.  I hope that explains what I mean; sorry I can't do better.

   Let's get this straight.

Yes, I used the terminology in an inexact (wrong) way, as I said in
recent postings.  I apologize.  I meant to say that C and C++ do not
have first-class nested functions that are block structured with
respect to their containing functions.

   3. Continuations are orthogonal to first-class functions (e.g. languages
      that allow both upward and downward funargs) and are orthogonal to
      block structure.

Hmm.  The only continuation-passing-style programs that I've ever seen
certainly spent a lot of time dealing with first-class objects that
were upward and downward funargs.  Perhaps you could sketch out an
extension to C that would provide continuations, without providing
such functional objects, and then I'd see what you're getting at.

dlw@odi.com (Dan Weinreb) (12/02/89)

In article <7159@ficc.uu.net> peter@ficc.uu.net (Peter da Silva) writes:

   In article <1989Nov29.230331.19595@odi.com> dlw@odi.com writes:
   > It depends what you mean.  It would certainly be possible to invent a
   > definition of a language that was like C, but also had the features
   > needed to do continuation-style programming.

   There is. It's called C. Almost.

   Seriously. All you need to do is have a bit of unportable code that mucks
   around in struct jmp_buf. Save the stack between here and there, do a setjmp
   to mark the end of the current stack, and then longjmp back to the saved
   spot. To continue, restore the saved stack and longjmp back to the mark.
   It would be advisable to write this code in assembly language :->.

   This would also give you the ability to add coroutines, threads, and all
   sorts of other good stuff. Let's call it ++C.

But it would not give me the ability to write programming that are in
what is traditionally (at the MIT AI lab -- OK, I'm provincial, but
that's where I was trained) referred to as continuation-passing
style".  When all the interesting papers about "continuation-passing
style" were coming out at MIT in the late '70s, they showed a lot of
very interesting techniques and things that you could do with
continuations.  But these techniques also employed funargs (both
kinds).  It's true that you could add the control structure all alone,
and call what you get "continuation-passing style", but it would not
be the style to which I had become accustomed.  Obviously my posting
was unclear because I had in mind this particular specialized meaning
of the phrase "continuation-passing style", and I didn't explain what
I meant.  Sorry about that.

grover%brahmand@Sun.COM (Vinod Grover) (12/02/89)

In article <31794@news.Think.COM> barmar@think.com writes:
>In article <128489@sun.Eng.Sun.COM> grover@sun.UUCP (Vinod Grover) writes:
>>I do not know if you are talking about first-class continuations or not, but
>>in languages which are stack-based first-class continuations are not trivial
>>to add. (By stack-based I mean that the procedure entry exit sequance
>>behaves in a stack-like fashion)
>
>This is a circular argument.  It's the very existence of first-class
>continuations in Scheme-like languages that causes them not to be
>stack-based.  If Scheme didn't have continuations then it would be
>stack-based, according to your definition.
One can also argue that it is the existence of lexical scoping, and
higher-order functions that cause a language not to be stack based. In this
sense, continuations are simply functions whose environment may be embedded
in other functions. 

Vinod Grover
Sun Microsystems

mikpe@majestix.ida.liu.se (Mikael Pettersson) (12/02/89)

In article <1989Dec1.205817.5178@Neon.Stanford.EDU> mxh@sumex-aim.Stanford.EDU (Max Hailperin) writes:
>As an asside to this discussion, it appears to me that closures can be
>emulated relatively smoothely in C++.
> [...]
>I haven't actually used this technique (I'm lucky enough to have a
>real lisp, instead of having to make C++ pretend it's one).  I'd be
>very interested to hear from anyone who has.


I have. The idea is this: whenever you want to create a closure with
type T, body B and (list of) free variables FV, you derive from an
abstract base class `funcT' a class `foo_funcT' with instance variables
`FV' and whose operator() does `B' when invoked. Something like this:

	(define (bar L)
	  (let ((i 1))
	    (for-each (lambda (item)
			(princ i)(princ ":")(print item)
			(set! i (1+ i)))
		      L)))

(assuming a left-to-right calling order in for-each, this should print
each item in the list L preceeded by its position in the list).
The C++ emulation could look like:


	class funcT {	// say: Obj->void
	public:
	    virtual void operator()(Obj);
	};
	class ObjList {
	    ...
	    friend void for_each(funcT&, ObjList);
	    ...
	};
	...
	class foo_funcT : public funcT {
	    int& i;
	public:
	    foo_funcT(int& j) : i(j) {}
	    void operator()(Obj);
	}
	void foo_funcT::operator()(Obj item) {
	    cout << i << ":" << item << "\n";
	    ++i;
	}
	void bar(ObjList L) {
	    int i = 1;
	    foo_funcT fun(i);
	    for_each(fun, L);
	}

This technique handles downwards funargs quite nicely, upwards funargs
require explicit heap allocation.
-- 
Mikael Pettersson, Dept of Comp & Info Sci, University of Linkoping, Sweden
email: mpe@ida.liu.se or ...!{mcsun,munnari,uunet,unido,...}!sunic!liuida!mpe

kend@tekchips.LABS.TEK.COM (Ken Dickey) (12/03/89)

Sorry, you got me in a bad mood.  FLAME ON!

In article <10008@june.cs.washington.edu> peterd@june.cs.washington.edu (Peter C. Damron) writes:
>
>The problem I have with including continuations into an object oriented
>language is a philisophical one.  Objects should be self-contained and
>hide information.  All interactions with objects should be through
>the well defined methods of the object.  Now, if you add continuations,
>there is the potential to pass the continuation outside of the object, thus
>creating another entry point into the object that is not a member function.
>I see this as an information hiding problem for continuations in OO languages.

So Smalltalk is not object oriented because it has blocks and returns
objects (remember that may be internal to/components of other
objects)?  How do you implement backtracking objects?  Having the
ability to deal with control flow in well structures ways is very
helpful.  It seems that you are trying to push everything through an
OO keyhole.  Functional interfaces are also well defined.  In
languages such as Scheme, you can program in functional, imparative,
OO, etc. styles as appropriate to the solutions you are trying to
structure.  There are real problems in trying to use a single paradigm
to cover all solutions.  Some just don't fit.

>Let's get this straight.
>...
>2. C and C++ do not allow nested functions, but they do support block
>   structure.  Thus the lexical scoping is not "trivial".

In C you get a local/global namespace.  You don't have name scoping in
blocks nested more than 1 deep.  That's pretty trivial.

>3. Continuations are orthogonal to first-class functions (e.g. languages
>   that allow both upward and downward funargs) and are orthogonal to
>   block structure.  It is not apparent to me that continuations are
>   any less useful without block structure (though C and C++ have it anyway).
>   It is also not apparent to me that continuations are any less useful
>   without nested functions (though maybe more cumbersome).

I suggest that you look at some of the Scheme literature for examples
(e.g. Abelson & Sussman: "Structure and Interpretation of Computer
Programs", MIT Press/McGraw-Hill, 1985).

>4. C and C++ have first class functions.  Functions can be passed as
>   parameters, returned as function values, and assigned to variables.

They cannot be unnamed.  They cannot be parameterized.  They are *not*
first class.

Perhaps a concrete example would help:

(define (curried-add x)
   (lambda (y) (+ x y)) )     ; returns an unnamed function

(define add3 (curried-add 3)) ; function is parameterized & bound
			      ; to a variable.

(add3 5) --> 8                ; usage: add 5 and 3 to get 8


>   The reason that C and C++ do not allow nested functions is precisely
>   so that they can have first-class functions implemented on a stack
>   (e.g. no closure required).  The "parent block" of a function is
>   simply the global variables.

Here you have something.  To quote from David Kranz's thesis ("ORBIT,
an optimizing Compiler for Scheme", Yale U, 1988; p. 9):
	  The important thing to realize is that Pascal and C are really just
	restricted subsets of Scheme, in particular those parts of Scheme
	which can be implemented efficiently using conventional compiler
	technology! 

You have to use non-standard compiler/runtime technology to compile
Scheme efficiently.  However, Scheme is a much smaller, more powerful
language than C.  When C++ becomes a language (when it is described in
a single document and has a well defined semantics) Scheme will be
smaller than C++ as well [the Scheme language report is ~50 pages,
including a formal semantics].

END-FLAME!

-Ken

boehm@flora.rice.edu (Hans Boehm) (12/04/89)

In article <5153@tekcrl.LABS.TEK.COM> kend@mrloog.WR.TEK.COM (Ken Dickey) writes:

>In C you get a local/global namespace.  You don't have name scoping in
>blocks nested more than 1 deep.  That's pretty trivial.
What about:

int i;
int f()
{
    int i;
    ...
    {
	int i;
	...
    }
}

I won't argue about whether this is trivial, but there are certainly nested
scopes.

>
>>3. Continuations are orthogonal to first-class functions (e.g. languages
>>   that allow both upward and downward funargs) and are orthogonal to
>>   block structure.  It is not apparent to me that continuations are
>>   any less useful without block structure (though C and C++ have it anyway).
>>   It is also not apparent to me that continuations are any less useful
>>   without nested functions (though maybe more cumbersome).
>
>I suggest that you look at some of the Scheme literature for examples
>(e.g. Abelson & Sussman: "Structure and Interpretation of Computer
>Programs", MIT Press/McGraw-Hill, 1985).

A more specific argument would help here.  I am reasonably familiar with
the Scheme literature, and it seems to me that most of the standard
examples involving continuations translate reasonably easily into a
world without closures, though they may be much less clean.  Call/cc is
probably not quite the right primitive.  An interface closer to
setjmp/longjmp (but without the restriction on the caller to setjmp
exiting, and with a cleaner distinction of what's saved, and what isn't)
probably makes more sense.  This would certainly be adequate for
implementing coroutines and the like.  It would be similar to
Call/cc(lambda x . (jmpbuf := x; 0)) in Scheme-like languages.

Note that unlike in Scheme, there is no analog to "continuation-
-passing-style" in C/C++.  But this is orthogonal to the above
discussion.

>
>>4. C and C++ have first class functions.  Functions can be passed as
>>   parameters, returned as function values, and assigned to variables.
>
>They cannot be unnamed.  They cannot be parameterized.  They are *not*
>first class.

I'm not sure what "They cannot be parametrized" means.  For most
interpretations of the sentence, it's wrong.

I would be among the first to argue that languages should provide real
higher-order functions.  However, it also seems clear that this requires
garbage collection of one form or another.  Furthermore it removes
some control over allocation behavior from the programmer.  Both of
these in turn cause some difficulty with programs operating under severe
real-time constraints.  C and C++ chose not to go this route.  Given that
they didn't, I know of no way to implement continuations efficiently,
while sticking to the design philsophy of either language.  Thus it doesn't
make sense to me to add first-class continuations to C++.  But this
argument has nothing to do with their utility to the programmer.

Hans-J. Boehm
boehm@rice.edu

allen@sbcs.sunysb.edu ( Allen Leung @ SUNY at Stony Brook, L.I., NY ) (12/04/89)

In article <3374@brazos.Rice.edu>, boehm@flora.rice.edu (Hans Boehm) writes:
> In article <5153@tekcrl.LABS.TEK.COM> kend@mrloog.WR.TEK.COM (Ken Dickey) writes:
> 
> >In C you get a local/global namespace.  You don't have name scoping in
> >blocks nested more than 1 deep.  That's pretty trivial.
> What about:
> 
> int i;
> int f()
> {
>     int i;
>     ...
>     {
> 	int i;
> 	...
>     }
> }
> 
> I won't argue about whether this is trivial, but there are certainly nested
> scopes.

   True, C has nested scopes, but only with non-functions.  We certainly
can't write 
   int i;
   int f()
   {
       int i;
       int g()
       {
        ... i ...
       };
       ...
   }

When we disallow local declaration of functions, I think all the sub-blocks 
can be lifted up to the main block with proper renaming of bound variables
( If you treat sub-blocks as lambda expression with a throw-away parameter, 
then they can always be beta-reduced at compile time. ) 
C with nested scopes actually has no more power( not the Turing Machine sense) 
than one without, even though its more convenient.  


> A more specific argument would help here.  I am reasonably familiar with
> the Scheme literature, and it seems to me that most of the standard
> examples involving continuations translate reasonably easily into a
> world without closures, though they may be much less clean.  Call/cc is
> probably not quite the right primitive.  An interface closer to
> setjmp/longjmp (but without the restriction on the caller to setjmp
> exiting, and with a cleaner distinction of what's saved, and what isn't)
> probably makes more sense.  This would certainly be adequate for
> implementing coroutines and the like.  It would be similar to
> Call/cc(lambda x . (jmpbuf := x; 0)) in Scheme-like languages.
> 
> Note that unlike in Scheme, there is no analog to "continuation-
> -passing-style" in C/C++.  But this is orthogonal to the above
> discussion.
> 
> >
> >>4. C and C++ have first class functions.  Functions can be passed as
> >>   parameters, returned as function values, and assigned to variables.
> >
> >They cannot be unnamed.  They cannot be parameterized.  They are *not*
> >first class.
> 
> I'm not sure what "They cannot be parametrized" means.  For most
> interpretations of the sentence, it's wrong.

    Maybe the original author means that C functions cannot reference non-global
free variables.   
    In any case, I'm extremely doubtful that continuation in a 
language with *side-effects* can be translated easily into a language 
without the concept of a closure.  Since a continuation is essentially a 
closure( entrypoint + environment ).  
    A language with side effects complicates the implementation of
closure/continuation tremendously:  A variable must reside in 
one place, either on the stack or in the heap; any duplication
will make the variable's update non-trivial.  
    I'm not familiar with Scheme, but I guess it only *solves* this
problem by allocation the stack frame of a possibly 'closurised'( you know
what I mean ) code on the heap, similar to what most lisp do with a upward 
funarg( or is it downward? I can never remember which is which.) 

    A language that supports continuation must/should support
first-class/higher order functions.  They have about the same complexity.
I believe we can simulate continuation with higher order functions
easily enough.  A continuation is just a function that never returns;  
that "never returns" part have some problem related to stack allocation, 
of course.

allen@sbcs.sunysb.edu

pcg@aber-cs.UUCP (Piercarlo Grandi) (12/04/89)

It has been my long held belief that really objects and closures are much
the same thing, to a certain extent. In many ways you can simulate closures
with objects (especially if you have suspend/resume) and objects with
closures.

My reckoning has always been that the most powerful possible entity is a
triple (dictionary, code, saved state), where each member of the triple may
be possibly null. If you also have the three steps for procedure invocation
directly available, like in SL5, then you can do wonderful things, and even
more incredible ones if you can have code executed at compile time (e.g.
like in Lisp, or in the "supercompiler" concept). The BETA language has
"patterns" that are really something like this.

As to more mundane things, under certain respects most current popular OO
languages are a step backwards from Simula 67, e.g. detach/suspend/resume,
inner, and block prefixing, etc...
-- 
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcvax!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk

grover%brahmand@Sun.COM (Vinod Grover) (12/05/89)

In article <1989Nov30.002204.4899@mentor.com> plogan@mentor.com (Patrick Logan) writes:
>
> >>From: grover%brahmand@Sun.COM (Vinod Grover)
> >                                       Admittedly, this is not as elegant as
> >Scheme's notion of continuation but workable in concept.
> >
>To be more accurate, longjump is not workable in concept because a
>longjump can only be executed once per setjmp. A continuation in
>Scheme can be captured once and executed zero or more times. Things
>become more complicated when you start considering nested setjmps
>and longjumps.
Yes, you are quite right. In fact the problem is even deeper than that, as I
just learned, if the function calling setjmp returns before the call to
longjmp is made then the longjmp is made to a non-existent environment. 
Thanks for correcting me.

Vinod Grover
Sun Microsystems.

jste@PacBell.COM (Joshua Stein) (12/05/89)

Can someone post a non-implementation dependent definition of continuations;
what they are, what they do, and how they fit into the object-oriented paradigm.
-- 
*******************************************************************************
Joshua Stein/Pacific*Bell/415 823-2411 |"I make it a rule to never get involved
the usual generic disclaimer goes here |with somone who's possessed ... well,
uucp:{world}!pacbell!pbhyf!jste	       |it's more of a guideline than a rule"

peter@ficc.uu.net (Peter da Silva) (12/06/89)

In article <1989Nov30.002204.4899@mentor.com> plogan@mentor.com (Patrick Logan) writes:
> To be more accurate, longjump is not workable in concept because a
> longjump can only be executed once per setjmp.

What if you (as I suggested in another post) save and restore the whole
stack?
-- 
`-_-' Peter da Silva. +1 713 274 5180. <peter@ficc.uu.net>.
 'U`  Also <peter@ficc.lonestar.org> or <peter@sugar.lonestar.org>.

      "If you want PL/I, you know where to find it." -- Dennis

djones@megatest.UUCP (Dave Jones) (12/06/89)

From article <1713@odin.SGI.COM>, by shap@delrey.sgi.com (Jonathan Shapiro):
> In article <10008@june.cs.washington.edu> peterd@june.cs.washington.edu (Peter C. Damron) writes:
>>4. C and C++ have first class functions.  Functions can be passed as
>>   parameters, returned as function values, and assigned to variables.
> 
>>Hope this helps to clear up any mis-understandings,
>>Peter.
> 
> Actually, Peter, it creates one.

Oh, gimme a break. I think the only misunderstanding is in the definition
of the value-charged adjectival phrase, "first class". If by "first class",
you mean "variable", then C's function-names are not "first class", but that
certainly does not make them second or third class! For most purposes,
constant-names for functions are to be prefered over function-valued variables.

>  C and C++ do not have fist class
> functions.  Functions cannot be passed as values.  Function pointers
> can, whcih is a somewhat different thing.  To be first class, it must
> be possible to treat functions as *values* rather than as *labels*.

What is a function's "value", if not a label?  What other candidates
are there? The only issue here is whether the name always refers to the
same function: Is the name effectively a label, or does it refer to a
variable which can contain any of a number of different labels? That is
the choice.

There are benefits to constant function-names. I can recall some awful,
XDR-related debugging sessions, struggling to figure out whether this or
that function-variable was bound to this or that procedure. It's best to
use constants when possible, reserving the use of variables for the
few occasions when they are really needed. Besides, on most machines, the
procedure call will be quicker if the name is a constant, resolved by
the linker, rather than at runtime.

> For example, the following is a syntax error:
> 
> int foo(int);
> int bar(int);
> 
> foo = bar
> 
> If functions were first class, this would be altogether legal.
> 

Then we would have to add something like ...

   constant int foo(int);

... to recover the lost functionality. I would much prefer that
function-names be constants in the default case.

Here's how you can have it both ways, constants and variables, in
plain old K&R C.

  extern int bar();    /* "bar" names an external constant. */
  int (*foo)();        /* "foo" names a variable. */

  void foozle()
  {
     foo = bar; /* What could be simpler? */
  }

nn@wheaties.ai.mit.edu (Nick Nussbaum) (12/07/89)

	Those interested in continuations in C might want to look
at a paper by Thomas Bruel which I believe was presented at the
Usenix 88 conference. He proposed implementing closures by
generating pointer to some automatically generated code which sets
up the environment and then jumps into the function. Thus closures
can be used in the same way as vanilla function pointers. He also
discusses the desirablility of nested functions.

-- 
Nick Nussbaum                      PO 68 - MIT Branch
nn@rice-chex.ai.mit.edu             Cambridge, MA 02139 

kend@tekchips.LABS.TEK.COM (Ken Dickey) (12/07/89)

In article <3374@brazos.Rice.edu> boehm@flora.rice.edu (Hans Boehm) writes:
>In article <5153@tekcrl.LABS.TEK.COM> kend@mrloog.WR.TEK.COM (Ken Dickey) writes:
>
>>In C you get a local/global namespace.  You don't have name scoping in
>>blocks nested more than 1 deep.  That's pretty trivial.
>What about:
>
>int i;
>int f()
>{
>    int i;
>    ...
>    {
>	int i;
>	...
>    }
>}
>
>I won't argue about whether this is trivial, but there are certainly nested
>scopes.

Try it with functions.

>...  I am reasonably familiar with
>the Scheme literature, and it seems to me that most of the standard
>examples involving continuations translate reasonably easily into a
>world without closures, though they may be much less clean.  Call/cc is
>probably not quite the right primitive.  An interface closer to
>setjmp/longjmp (but without the restriction on the caller to setjmp
>exiting, and with a cleaner distinction of what's saved, and what isn't)
>probably makes more sense.  This would certainly be adequate for
>implementing coroutines and the like.  It would be similar to
>Call/cc(lambda x . (jmpbuf := x; 0)) in Scheme-like languages.

The literature I was thinking of is on the order of:

 Wand, Mitchell: "Continuation-Based Program Transformation Strategies"
 JACM V 27, #1, January 1980

 Friedman, Haynes, & Kohlbecker: "Programming with Continuations"
 in "Program Transformations and Programming Environments"
   Ed: P. Pepper
   Springer-Verlag 1984

 Haynes, Friedman, & Wand: "Obtaining Coroutines with Continuations"
 Computer Languages: V11, #3/4 1986

 Haynes, Friedman, & Wand: "Continuations & Coroutines"
 1984 Symposium on Lisp and Functional Programming

 Friedman, Haynes: "Constraining Control"
 Proceedings 12th Annual Symposium on 
 Principles Of Programming Languages (ACM), January 1985

 Wand: "Continuation-Based Multiprocessing"
 Proceedings of the 1980 Lisp Conference

 Felleisen, Friedman, Kohlbecker, & Duba: "Reasoning with Continuations"
 Proceedings of the Symposium on Logic in Computer Science, IEEE Press, 1986

 Haynes & Friedman: "Engines Build Process Abstractions"
 1984 Symposium on Lisp and Functional Programming

 Haynes & Friedman: "Abstracting Timed Preemption with Engines"
 Computer Languages, V20, #2, 1987 (pub Great Britain).

 Clinger, Will: "The Scheme Environment: Continuations"
 Lisp Pointers, V1, #2, June-July 1987


I guess I don't quite see what you are looking for here.  With a timer
interrupt, you can implement multitasking.  Other applications [8^] of
continuations are non-blind backtracking, dynamic-wind, reasonable
error recovery (correct & reexecute), etc.  Yes, you can do these
things in the os/runtime environment (outside of the C language).
Yes, you can do this in less clean ways.  Yes, call/cc is not
necessarily intuitive to think about.  Why do you want something less
clean?  How do you `reexecute' code in sane ways without a segmented
stack?

>>>4. C and C++ have first class functions.  Functions can be passed as
>>>   parameters, returned as function values, and assigned to variables.
>>
>>They cannot be unnamed.  They cannot be parameterized.  They are *not*
>>first class.
>
>I'm not sure what "They cannot be parametrized" means.  For most
>interpretations of the sentence, it's wrong.

I am thinking of parameterization by currying.  With higher order
functions, you create families of functions parameterized/specialized
for particular tasks.  E.g. a function which does numerical
integration becomes a specialized integrator by being parameterized
with a particular function and can then be used to integrate various
intervals.  A generic device interface becomes specialized for a
particular hardware environment by being parameterized by a particular
HW device driver.  Et cetera.  Sorry for the confusion.

>
>I would be among the first to argue that languages should provide real
>higher-order functions.  However, it also seems clear that this requires
>garbage collection of one form or another.  

I have yet to see an open system without a memory allocator.
Languages without a `pointer' abstraction save a certain class of
errors (i.e. one does not dereference bogus pointers).  Dealing with
*values* and having the language implementation deal with what fits in
a register and what does not can save significantly in implementation
time.  

I do not view automatic storage management as a bad thing.  The "GC in
an uncooperative environment" people observed that even with partial
collection of C code, they were able to take care of storage leaks
which they found in almost all large C programs they looked at.  Using
more memory to get faster development may buy market share where
time-to-market is important.

>... Furthermore it removes
>some control over allocation behavior from the programmer.

I don't fully agree with this.  Don't use CONS.  Allocate data
statically.  Parameterize your functionals at compile time.  Demand and
use non-cons runtime services.

> .. Both of
>these in turn cause some difficulty with programs operating under severe
>real-time constraints.  C and C++ chose not to go this route.

Again, I don't fully agree.  It depends on how close you are to the
limits.  Some C implementations have allocators which don't allocate
in constant time (e.g. best fit) and fail in the real-time area.  Some
highly constrained RT systems are coded in assembler because C is too
slow.  Some real time applications have enough margin to collect
adequately if coded in low-cons style.  The Appel-Ellis-Li real-time
collector (e.g.  with a 68030) looks very interesting.  I think that
the general picture is unclear enough here that it is difficult to
make a blanket statement about real-time systems in general.

> ... Given that 
>they didn't, I know of no way to implement continuations efficiently, 
>while sticking to the design philsophy of either language.  Thus it doesn't 
>make sense to me to add first-class continuations to C++.  But this 
>argument has nothing to do with their utility to the programmer.  
> 
>Hans-J. Boehm 
>boehm@rice.edu

Questions of design philosophy aside, it is interesting to look more
closely at what `efficiency' is being asked for.  

The ability to mix compiled and interpreted code gives a very short
design-code-test loop.  Not dereferincing bogus pointers eliminates a
class of programming errors.  *Efficiency in bringing products rapidly
to market.

Having first class functions help encapsulation and can lead to
smaller, cleaner code and more code reuse--which means lower
maintainance costs.  This means more production efficiency because
engineers can be building new product rather than doing maintenance.
*Efficiency in product production; less rework, less spoilage.

I guess if you are not building production code, you don't care about
time-to-build/time-to-market.  But you probably aren't worried about
hard real-time performance either.

=====

Having gone this far, I guess I should make a philosoply statement and
do some language bashing so people really have something to flame
about.  [ Don't get too excited; but I do like a good discussion! ]

I think the C is a great language for writing device drivers.  When it
comes to do doing large systems (>200-500 KLOC), system complexity
becomes a larger dragon than execution speed.  C has won because of
its simple execution model, closeness to assembler, and Un*x usage.
In C++, the simple execution model is lost [An assignment statement
may copy data 3 times.  Strings may be reference counted within the
parameter passing mechanism.  Arrays may not be allocated until first
use.  Et cetera.]  I know a number of people programming in C++ and
none who is happy with it.  Using a very complex artifact [a Language
has a single defining document and a well understood semantics] like
C++ does not help in minimizing system complexity.

Go ahead! Call me a language bigot! I deserve it!  I have also written
50-100 KLOC each of Pascal and C code, some Smalltalk, APL, FP,
Fortran, and a fair amount of Scheme code.  I find that Scheme gets in
my way the least.  Having said that, I think that implementation
technologies only make sense in the context of implementations.  In
some contexts, the language of choice may be C, Basic, Beta, Scheme,
Ada, assembler, etc.  I go for the best technology I can to solve
problems in a given context.  I think that the important thing is to
know more than 1 technology so that one knows what reasonable choices
exist and how to evaluate what is reasonable.

[Please, flames to /dev/null -- I freely admit, I don't know it all].

[Non-continuation follow-ups probably belong somewhere other than
comp.object] 

-Ken

beard@ux1.lbl.gov (Patrick C Beard) (12/08/89)

In article <5153@tekcrl.LABS.TEK.COM> kend@mrloog.WR.TEK.COM (Ken Dickey) writes:
>In article <10008@june.cs.washington.edu> peterd@june.cs.washington.edu (Peter C. Damron) writes:
>>Let's get this straight.
>>...
>>2. C and C++ do not allow nested functions, but they do support block
>>   structure.  Thus the lexical scoping is not "trivial".
>
>In C you get a local/global namespace.  You don't have name scoping in
>blocks nested more than 1 deep.  That's pretty trivial.

Not true.  In C you have a global namespace, a struct namespace, and multiple
nested local namespaces.  For example:

int x;	/* in global namespace. */

struct foo {
	int x;	/* struct namespace. */
};

routine()
{
	int x;	/* first local namespace. */

	{
		int x;	/* first of many possible nested local namespaces. */
				/* you can nest as deep as you like. */
	...
	}
}

At every new scope, the x's in the outer scopes are hidden.  Why does this
constitute "trivial" name scoping?

Sorry if this response was long winded.  Let's quit quibbling over 
legitimate language differences.  Some languages are good for one thing,
some another, none are suitable for every programming task.


-------------------------------------------------------------------------------
-  Patrick Beard, Macintosh Programmer                        (beard@lbl.gov) -
-  Berkeley Systems, Inc.  ".......<dead air>.......Good day!" - Paul Harvey  -
-------------------------------------------------------------------------------

chase@Ozona.orc.olivetti.com (David Chase) (12/08/89)

In article <5369@rice-chex.ai.mit.edu> nn@rice-chex.WISC.EDU (Nick Nussbaum) writes:
>	Those interested in continuations in C might want to look
>at a paper by Thomas Bruel which I believe was presented at the
>Usenix 88 conference. He proposed implementing closures by
>generating pointer to some automatically generated code which sets
>up the environment and then jumps into the function. Thus closures
>can be used in the same way as vanilla function pointers.

It's a neat trick (Stallman put me in touch with Thomas Breuel, who
described it to me), and I even used it for a while in a Modula-3
compiler.  Most unfortunately, life gets much more complicated on
architectures/OSes that separate instructions and data (by page
protection, or in potentially inconsistent caches).  Some calling
conventions also make this difficult -- it's dead easy on a Sun 1, 2,
or 3, less easy on a SPARC, and I'm not sure that it's tractable at
all on a MIPS, i860, or 88000.  Bummer, that.  It was great fun while
it lasted.

David

gateley@m2.csc.ti.com (John Gateley) (12/12/89)

In article <6527@pbhyf.PacBell.COM> jste@PacBell.COM (Joshua Stein) writes:
>Can someone post a non-implementation dependent definition of continuations;
>what they are, what they do, and how they fit into the object-oriented paradigm.

A continuation of some point during the execution of a program is a function
which accepts any results at the point, and returns the result of the
rest of the program. To be more technical, you need to define points occuring
during execution and so on.

Basically continuations are a different flavor of goto. They can do forward
jumping (called escape continuations) where you want to punt and jump
out of 20 levels of function calls at once. They can do backwards jumping
where you invoke the continuation after you passed the point in the program
which created it (causing looping).

They are used to model different control constructs. Coroutines are the
canonical example; it is very easy to model coroutines given continuations.
They are extremely powerful, and consequently extremely dangerous, and
also tend to be as controversial as gotos.

I don't know how they fit in an object oriented paradigm.

John
gateley@m2.csc.ti.com

ge@kunivv1.sci.kun.nl (Ge' Weijers) (12/13/89)

chase@Ozona.orc.olivetti.com (David Chase) writes:
>However, the languages listed differ in their treatment of nested
>procedures:

>C         forbids them  (no closures).

>Pascal    allows them, but (in Pascal Classic, at least) forbids
>          their assignment to variables, use as parameters to
>	  procedures, or use in a RETURN.  (no closures, because it
>	  turns out that a display can be used).

Not quite true, in the original Pascal procedures were allowed as parameters,
though type-safety goes out of the window, as the parameters were not indicated.

PROCEDURE example (PROCEDURE error);
BEGIN
	IF NOT (assertion)
	THEN error(4711);
END example;

I believe something like this was actually legal. In more current versions of
the language the procedure must be typed.
mr. Wirth probably did not dare to throw away the Algol-60 call by name
at the time, as some people thought is was useful. They still are.

>Modula-3  allows them, but forbids their assignment to variables or
>          their use in a RETURN.  (closures needed, but must obey
>	  stack lifetime rules).  (I hope this example isn't too
>	  obscure -- I'm rather familiar with it at the moment).

Just like in Algol 68. Only the lifetime rule was very tricky there:
the lifetime of a procedure is the lifetime of the 'youngest' non-local
name accessed. The lifetime of a function not referencing any globals was
therefore infinite, whereever it was declared.
Ge' Weijers                                    Internet/UUCP: ge@cs.kun.nl
Faculty of Mathematics and Computer Science,   (uunet.uu.net!cs.kun.nl!ge)
University of Nijmegen, Toernooiveld 1         
6525 ED Nijmegen, the Netherlands              tel. +3180612483 (UTC-2)