[comp.lang.scheme] Simulating Environments with Continuations ?

brian@granite.jpl.nasa.gov (Brian of ASTD-CP) (06/28/91)

Hello, schemers.  This is a little long, so hit 'n' if you're
not interested in the subject...

We here at JPL are striving to develop some GUI
libraries for Scheme.  We want to use object-oriented programming,
specifically, multiple method inheritance.  We very quickly ginned
up something based on message-passing with superclass precedence 
lists, that works great.  Sketchily, a class is represented by its
constructor procedure.  The constructor makes a lexical block con-
taining instance variables and method procedures and returns a
message-dispatching procedure.  Like this:

(define ( <class-constructor> [args])

  (let (( <ivar1> <init-val1> )
	( <ivar2> <init-val2> ) ...)

    (define ( <method-proc1 [args]> )
      ; reference ivars as free vars )

    (define ( <method-proc2 [args]> )
      ; reference ivars as free vars )
    ...
    (define (self msg [args])
      (case msg
	(msg1 <method-proc1> )
	(msg2 <method-proc2> ) ...)) self))

To instantiate an object (instance of a class), call the cons-
tructor.  The instance is represented by the message dispatch
procedure ("self") returned.  All the methods have access to the
instance vars _via_ lexical scoping.  

Ok, the only problem is that each instance of a class will have
its own copy of the method procedures.  This is wasteful of space,
since all the copies of each method proc are identical, having 
been "define"d by the same source code.  What we want to do is 
solve the problem through method sharing.  

1. We want the method procedures to be "define"d just once; 

2. we want the names of the method procedures to be lexically 
   hidden so they won't collide with global names; and 

3. we want the method proc's to be able to access instance 
   variables.  

As an approach to getting all these things, we've realized that 
encapsulating instance vars in first-class environments and passing
the environments to method procedures would let us do #3.  We
could get #1 and #2 by encapsulating method proc definitions in an
environment, too.  Trouble is, environments are not part of Scheme.

Some implementations have environments, it's true, but we can't
afford to marry a product line; we have to go standard.  We can imp-
lement environments ourselves _a la_ metacircular evaluation (ch. 4
of SICP), but that takes us VERY far afield of what we're trying
to do.  Also, we can't use T because we can't port (the back end) of
T.  We are using "workstations of the future," i.e. we don't know
now what machines we will want to run our software on.  Therefore,
we must have the source to our Scheme implementation and we must
know ourselves how to port it.  Currently, we're looking into 
the intersection of Scheme->C and Mit C Scheme (C Scheme has envi-
ronments, but Scheme->C doesn't.  Also, if we can't learn, by in-
spection, how to port either or both of them to the TBD RISC machine
of the future, then we'll look for something else like Elk or 
XScheme).

So, how do we accomplish #1, #2, & #3 in std. Scheme?  My intuition
tells me that continuations, and some kind of continuation-passing
style will do it, perhaps treating methods as co-routines, but I've
been unable to devise a clean, simple way of doing it.  

Any suggestions would be welcome, either using continutations or 
some other technique that we've overlooked.  

\brian\

 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 . . Brian Beckman . . . . . . . . . . brian@granite.jpl.nasa.gov. . . .
 . . meta-disclaimer: every statement in this message is false . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

markf@zurich.ai.mit.edu (Mark Friedman) (06/28/91)

In article <1991Jun27.192117.8609@jato.jpl.nasa.gov>
brian@granite.jpl.nasa.gov (Brian of ASTD-CP) writes:

   (define ( <class-constructor> [args])

     (let (( <ivar1> <init-val1> )
	   ( <ivar2> <init-val2> ) ...)

       (define ( <method-proc1 [args]> )
	 ; reference ivars as free vars )

       (define ( <method-proc2 [args]> )
	 ; reference ivars as free vars )
       ...
       (define (self msg [args])
	 (case msg
	   (msg1 <method-proc1> )
	   (msg2 <method-proc2> ) ...)) self))

   To instantiate an object (instance of a class), call the cons-
   tructor.  The instance is represented by the message dispatch
   procedure ("self") returned.  All the methods have access to the
   instance vars _via_ lexical scoping.  

   Ok, the only problem is that each instance of a class will have
   its own copy of the method procedures.  This is wasteful of space,
   since all the copies of each method proc are identical, having 
   been "define"d by the same source code.  What we want to do is 
   solve the problem through method sharing.  

Unless your interpreter is going out of its way to be wasteful and
silly (and unless it was careful it would be incorrect for the most
general case) the above approach will not copy the method-proc's in
each instance. Each instance (a closure) will have a pointer to the
environment which contains the method-procs.

-Mark
--

Mark Friedman
MIT Artificial Intelligence Lab
545 Technology Sq.
Cambridge, Ma. 02139

markf@zurich.ai.mit.edu

norman@parc.xerox.com (Norman Adams) (06/28/91)

>>In article <1991Jun27.192117.8609@jato.jpl.nasa.gov>
>>brian@granite.jpl.nasa.gov (Brian of ASTD-CP) writes:
>
>>   (define ( <class-constructor> [args])
>>
>>     (let (( <ivar1> <init-val1> )
>>	   ( <ivar2> <init-val2> ) ...)
>>
>>       (define ( <method-proc1 [args]> )
>>	 ; reference ivars as free vars )
>>
>>       (define ( <method-proc2 [args]> )
>>	 ; reference ivars as free vars )
>>       ...
>>       (define (self msg [args])
>>	 (case msg
>>	   (msg1 <method-proc1> )
>>	   (msg2 <method-proc2> ) ...)) self))

>markf@zurich.ai.mit.edu (Mark Friedman) writes:
>Unless your interpreter is going out of its way to be wasteful and
>silly (and unless it was careful it would be incorrect for the most
>general case) the above approach will not copy the method-proc's in
>each instance. Each instance (a closure) will have a pointer to the
>environment which contains the method-procs.

Unless your interpreter is very smart, the environment of each
instance will include a first class procedure for each method.  Even
if the environment is shared, that is proabably 2 words per method,
for each instance.  I think we all agree that the size of an instance
should independent of the number of methods it implements.

I think the right approach is to have the compiler support this idiom
(or an equivalent one).  It is not very hard, just a small twist in
your closure analysis.   The T compiler does this.  

This topic is discussed in:

%A Norman Adams
%A Jonathan Rees
%T Object-Oriented Programming in Scheme
%J Conference Record of the 1988 ACM Conference on Lisp
and Functional Programming
%P 277-288
%D August 1988
%K oopinscheme

-Norman Adams
norman@parc.xerox.com

markf@zurich.ai.mit.edu (Mark Friedman) (06/29/91)

In article <norman.678125326@crevenia> norman@parc.xerox.com (Norman Adams) writes:

   Unless your interpreter is very smart, the environment of each
   instance will include a first class procedure for each method.

You're quite right. I was thinking of something like:

   (define <class-constructor>
     (let ()
       (define ( <method-proc1 [args]> )
	 ; reference ivars as free vars )

       (define ( <method-proc2 [args]> )
	 ; reference ivars as free vars )
       ...
       (define ( <class-constructor> [args])

	 (let (( <ivar1> <init-val1> )
	       ( <ivar2> <init-val2> ) ...)

	   (define (self msg [args])
	     (case msg
		   (msg1 <method-proc1> )
		   (msg2 <method-proc2> ) ...)) self))
       <class-constructor>))

which, of course, doesn't work.

   I think the right approach is to have the compiler support this idiom
   (or an equivalent one).  It is not very hard, just a small twist in
   your closure analysis.   The T compiler does this.  

But the original author wanted something which would work in a
standard Scheme.

Originally (before I came up my erroneous claim), I was going to
respond to the original author by suggesting a macro (e.g.
`WITH-INSTANCE-VARIABLES') which would take a class as an argument and
walk over his method code replacing variables references (and
settings) with appropriate access (and setting) procedures. In that
scheme instance variables would probably become slots in some data
structure held in the instance closure.  It's hairy, but not
unreasonable. Of course macros are not yet standard, but at least the
RnRS authors are committed to putting them in R4RS.

-Mark
--

Mark Friedman
MIT Artificial Intelligence Lab
545 Technology Sq.
Cambridge, Ma. 02139

markf@zurich.ai.mit.edu