[comp.lang.lisp] CLOS: is it OOP?

folta@tove.umd.edu (Wayne Folta) (09/14/89)

I just got Allegro Common Lisp for my Mac, and I have a question concerning
CL's proposed object-oriented extension, CLOS.  I have been reading a book
which implies that CLOS can be argued to not be OO:

"Because CLOS makes no effort at protection or encapsulation, some might
find that it lacks the most essential thing for an object-oriented system."

"Generic functions are a step away from the concept of active data and back
toward the conventional division between passive data and active process."

Of course, the author has wonderful things to say about CLOS as well, and
praises its generic-function approach as one of Lisp's great contributions.

This could start firestorm here, but I don't completely understand OOPS,
and it would be nice to know what experts think about CLOS's OOP-edness.
(Apple's CL has Object Lisp extensions, and I have ordered Xerox's
CommonLOOPS, both of which I understand to be more traditionally "OOP-like"
than CLOS.)


Wayne Folta          (folta@tove.umd.edu  128.8.128.42)

welch@peirce.cis.ohio-state.edu (Arun Welch) (09/14/89)

> and I have ordered Xerox's CommonLOOPS
Huh? CommonLoops was an early name for PCL (Portable Common Loops),
which has been the primary implementation engine for CLOS.  Someone's
pulling your leg. 

Anyway, I've used both PCL and Loops extensively, and can say that
CLOS *is* an OOPS, just in a different style than the other OOPS, where
method calls were active sends.  If you're really hung up on using
send, it's pretty trivial to define a macro to dispatch the
appropriate method.

...arun


----------------------------------------------------------------------------
Arun Welch
Lisp Systems Programmer, Lab for AI Research, Ohio State University
welch@tut.cis.ohio-state.edu

john@linus.UUCP (John D. Burger) (09/15/89)

Wayne Folta writes:
> ... I have been reading a book which implies that CLOS can be argued
> to not be OO:
>
>"Because CLOS makes no effort at protection or encapsulation, some might
>find that it lacks the most essential thing for an object-oriented system."
>
>"Generic functions are a step away from the concept of active data and back
>toward the conventional division between passive data and active process."

I suppose the author was referring to things like internal functions
and methods.  For example, one might define the SPECIAL-MULTIPLY
function to be internal to the COMPLEX-NUMBER class.  This guarantees
that SPECIAL-MULTIPLY may only be called within methods attached to
COMPLEX-NUMBER.

A problem with this, in particular, is that CLOS allows multiple
arguments of methods to be specialized.  Thus, it's not clear that it 
makes sense to think of methods as "attached" to classes.  I suppose
the metaphor could be extended for multi-methods, but I'm not sure if
it's worth it.  In any case, it's straight-forward to specialize
method classes and meta-classes in CLOS so as to have internal
functions and methods.  In particular, I imagine it could be done by
specializing the way applicable methods are computed.

As for other instances of protection and encapsulation, I think they
could be implemented as well.  That's the great thing about CLOS:
since CLOS is itself a CLOS program, you can beat the hell out of it
to get just about any sort of meta-level behavior.


-- 
John Burger                                               john@mitre.org

"You ever think about .signature files? I mean, do we really need them?"
  - alt.andy.rooney

bill@cambridge.apple.com (Bill St. Clair) (09/15/89)

In article <19582@mimsy.UUCP> folta@tove.umd.edu.UUCP (Wayne Folta) writes:
>I just got Allegro Common Lisp for my Mac, and I have a question concerning
>CL's proposed object-oriented extension, CLOS.  I have been reading a book
>which implies that CLOS can be argued to not be OO:
>
>"Because CLOS makes no effort at protection or encapsulation, some might
>find that it lacks the most essential thing for an object-oriented system."
>
>"Generic functions are a step away from the concept of active data and back
>toward the conventional division between passive data and active process."
>
>Of course, the author has wonderful things to say about CLOS as well, and
>praises its generic-function approach as one of Lisp's great contributions.
>
>This could start firestorm here, but I don't completely understand OOPS,
>and it would be nice to know what experts think about CLOS's OOP-edness.
>(Apple's CL has Object Lisp extensions, and I have ordered Xerox's
>CommonLOOPS, both of which I understand to be more traditionally "OOP-like"
>than CLOS.)

Xerox's CommonLOOPS, usually known as PCL (Portable Common LOOPS) is
an implementation of CLOS, sans Meta Object Protocol.  There ARE some
real differences between the object systems you mention, but most of
them are syntactic details.  CLOS stores a table of methods indexed by
type inside of a generic function.  Flavors (and Smalltalk, I believe)
stores a table of methods indexed by message name inside of a class
structure.  Object Lisp stores a table of functions indexed by name
inside of each instance (it has no real notion of class), and looks up
the kindof hierarchy. The only real difference here is the search
order (and implementations are free to represent things differently:
e.g. a flavors implementation could decide to associate a table of
class types with each message name).  Object Lisp has the nice feature
that an instance can have its own private method not associated with
any class, but this is not used very much. CLOS handles this with EQL
methods.  Yes, CLOS does not provide encapsulation: once a generic
function is defined, it's argument pattern is set for all time.
Encapsulation is handled by the Common Lisp package system (Here's
your chance, all you package system flamers).

OOP is a nice way of seperating  code into small pieces.  It's
primary use in my experience is that it allows me to  modify the
behavior of somebody else's code without actually touching, and
potential breaking, the code itself.  It also allows me to build basic
functionality without worrying about all the little particulars and
add them later.  The three object-oriented systems that I've used
(Flavors, Object Lisp, CLOS) all provide these features.  Exactly
which details  make their way into the standard is not nearly as
important to me as that the standard is agreed upon and set so that we
can all talk the same language and share our code.

barmar@think.COM (Barry Margolin) (09/16/89)

In article <19582@mimsy.UUCP> folta@tove.umd.edu.UUCP (Wayne Folta) writes:
>"Because CLOS makes no effort at protection or encapsulation, some might
>find that it lacks the most essential thing for an object-oriented system."

There is as yet no general concensus on what is the "most essential
thing for an object-oriented system".  Find the proceedings of any
recent OOPSLA conference and you'll almost certainly find a panel
discussion about this very topic, and every panelist will have a
different idea of what is important.

Probably the only thing common to all object systems is some notion of
inheritance.

I'm also a fan of encapsulation and protection, but I don't think that
they are absolutely required for an object system.  CLOS probably
lacks them mostly because Flavors lacks them, and Flavors probably was
copying DEFSTRUCT-style inheritance.  It could also be the general
Lisp philosophy of allowing the programmer as much freedom as
possible.  DLW, do you know why HIC implemented Flavors this way?

>"Generic functions are a step away from the concept of active data and back
>toward the conventional division between passive data and active process."

The distinction between generic functions and active data is mostly
syntactic.  Can you really see any semantic difference between

	(send object 'foo ...)
and
	(foo object ...)

Either one can readily be defined in terms of the other:

Generic function in terms of application:
	(defun foo (object &rest args)
	  (lexpr-send object 'foo args))

Application in terms of generic function:
	(defun send (object message &rest args)
	  (apply message object args))

The major benefit of generic functions is that it is like the rest of
Lisp.  You can pass them around wherever ordinary functions are
permitted.  For instance, they can be passed to mapping functions.  In
a message-sending system, programs that accept messages generally
don't accept functions and vice-versa; you end up writing
	#'(lambda (x) (send x 'foo))
alot.

Barry Margolin
Thinking Machines Corp.

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

dlw@odi.com (Dan Weinreb) (09/16/89)

In article <19582@mimsy.UUCP> folta@tove.umd.edu (Wayne Folta) writes:

   "Because CLOS makes no effort at protection or encapsulation, some might
   find that it lacks the most essential thing for an object-oriented system."

This overstates the case.  CLOS, following Lisp philosophy, relies on
programmers to follow conventions, rather than actually imposing
access control; this is particularly helpful in interactive debugging.
In CLOS you can still say whether you want accessor functions or not
for each slot.

   "generic functions are a step away from the concept of active data and back
   toward the conventional division between passive data and active process."

This is nonsense.  The author of your book cannot distinguish syntax
from semantics, or at least worries too much about syntax.  CLOS
supports "active data" every bit as much as Smalltalk-80 does.  Even
though the character string "send" isn't used, exactly the same thing
is going on (except that CLOS is more general, having multimethods).
CLOS uses the syntax of function calling in order to fit into Lisp
better.

(Historical note: The first implementation of Flavors, 1981-1986 or
so, did use "send".  After many years of heavy experience, we found
that it would make a lot of things easier, clearer, and more elegant
to drop the "send" and use the syntax of function calls to perform
"message sending"/ "generic functions"/"virtual functions".  New
Flavors did this.  CommonLoops did the same thing, and it was retained
in CLOS.)

pallas@Neon.Stanford.EDU (Joe Pallas) (09/16/89)

In article <29564@news.Think.COM> barmar@think.COM (Barry Margolin) writes:

>I'm also a fan of encapsulation and protection, but I don't think that
>they are absolutely required for an object system.  CLOS probably
>lacks them mostly because Flavors lacks them, and Flavors probably was
>copying DEFSTRUCT-style inheritance.

and

>The distinction between generic functions and active data is mostly
>syntactic.  Can you really see any semantic difference between

>	(send object 'foo ...)
>and
>	(foo object ...)

I'm inclined to agree that protection is not absolutely required for
an object-oriented language (although I wouldn't want to use one
without it).  But I would argue that it is precisely because of
generic functions that CLOS CANNOT have protection.  To have
protection, you must have a clearly defined protection boundary.
Method invocations establish such a boundary, but generic functions do
not.

>The major benefit of generic functions is that it is like the rest of
>Lisp.  You can pass them around wherever ordinary functions are
>permitted.  For instance, they can be passed to mapping functions.  In
>a message-sending system, programs that accept messages generally
>don't accept functions and vice-versa; you end up writing
>	#'(lambda (x) (send x 'foo))
>alot.

I can't say for sure, but I don't think this is true unless you spend
a lot of time writing named helper functions in CommonLisp.  I just
scanned through my Smalltalk sources and couldn't find any such
trivial lambda expressions (blocks, in Smalltalk parlance).  You
almost always want to do more than just send a simple message in such
a case.  So you either have to write a special purpose method/generic
function, or you use a lambda expression anyway.

joe

barmar@think.COM (Barry Margolin) (09/17/89)

In article <11815@polya.Stanford.EDU> pallas@Neon.Stanford.EDU (Joe Pallas) writes:
>I'm inclined to agree that protection is not absolutely required for
>an object-oriented language (although I wouldn't want to use one
>without it).  But I would argue that it is precisely because of
>generic functions that CLOS CANNOT have protection.  To have
>protection, you must have a clearly defined protection boundary.
>Method invocations establish such a boundary, but generic functions do
>not.

I don't understand this; perhaps we are talking about different kinds
of protection.  What kind of boundary is established by (send object
:foo) that is not established by (foo object)?

My definition of encapsulation and protection is that methods for a
particular class may only access instance variables defined by that
class.  Flavors and CLOS permit methods to access any of the object's
instance variables directly, even instance variables defined by a
sibling class.  Flavors, at least, has options such as
:REQUIRED-INSTANCE-VARIABLES, which permit such dependencies to be
documented and checked at instantiation time, but CLOS doesn't.

>>In
>>a message-sending system, programs that accept messages generally
>>don't accept functions and vice-versa; you end up writing
>>	#'(lambda (x) (send x 'foo))
>>alot.
>
>I can't say for sure, but I don't think this is true unless you spend
>a lot of time writing named helper functions in CommonLisp.  I just
>scanned through my Smalltalk sources and couldn't find any such
>trivial lambda expressions (blocks, in Smalltalk parlance).  You
>almost always want to do more than just send a simple message in such
>a case.  So you either have to write a special purpose method/generic
>function, or you use a lambda expression anyway.

I've done it many times.  Often you want to send a single message to
every element of a list, e.g.

	(mapc #'(lambda (stream) (send stream :close)) *all-open-streams*)

In a generic function system this becomes simply.

	(mapc #'close *all-open-streams*)

In Smalltalk (which I only have the most meagre experience, and I
can't find my reference book here at home, so excuse any syntax
errors), I think this would look something like

	allOpenStreams :MapList
	  [stream | stream :Close]

This particular case isn't much of a problem, because you can use a DO
loop to get reasonable code in the message system:

	(dolist (stream *all-open-streams*)
	  (send stream :close))

However, another example that is harder to rewrite without the lambda
expression is:

	(find "foo" *all-open-streams* :test #'string-equal
	      :key #'(lambda (stream) (send stream :file-name)))

which becomes

	(find "foo" *all-open-streams* :test #'string-equal :key #'file-name)

in a generic function system.  Trivial little accessors are used all
the time in code like this.  Perhaps Smalltalk doesn't have as many
utility routines like this that take functional arguments, which would
explain why you don't find so many trivial blocks in your code.

Barry Margolin
Thinking Machines Corp.

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

dlw@odi.com (Dan Weinreb) (09/17/89)

In article <29564@news.Think.COM> barmar@think.COM (Barry Margolin) writes:

   I'm also a fan of encapsulation and protection, but I don't think that
   they are absolutely required for an object system.  CLOS probably
   lacks them mostly because Flavors lacks them, and Flavors probably was
   copying DEFSTRUCT-style inheritance.  It could also be the general
   Lisp philosophy of allowing the programmer as much freedom as
   possible.  DLW, do you know why HIC implemented Flavors this way?

I would say that Flavors *did* have "encapsultation and protection".
In the original Flavors, instance variables by default are NOT
"gettable", "settable", or "initable".  This means that the only code
that can access them is code inside methods.  If that's not
"encapsultation and protection", I'm not sure what is; if you had
something specific in mind, what was it?

dlw@odi.com (Dan Weinreb) (09/17/89)

In article <11815@polya.Stanford.EDU> pallas@Neon.Stanford.EDU (Joe Pallas) writes:

   I'm inclined to agree that protection is not absolutely required for
   an object-oriented language (although I wouldn't want to use one
   without it).  But I would argue that it is precisely because of
   generic functions that CLOS CANNOT have protection.  To have
   protection, you must have a clearly defined protection boundary.
   Method invocations establish such a boundary, but generic functions do
   not.

Yes, there's some truth in this.  It's not the generic functions per
se that cause the problem though, since New Flavors has generic
functions, but does have a clearly defined protection boundary.  In
New Flavors, a method of a flavor can access instance variables
directly, and other functions cannot.  What causes the trouble for
CLOS is the addition of the multimethod concept, and all the changes
that it implies: there is no such thing as a "method of a class" in
CLOS, because one function might be involved with more than one class
at the same time.  I don't think this would have made it impossible to
put a protection boundary into CLOS, but it certainly would have made
it harder, and CLOS certainly can't be said to have one, given
get-slot.

   >The major benefit of generic functions is that it is like the rest of
   >Lisp.  You can pass them around wherever ordinary functions are
   >permitted.  For instance, they can be passed to mapping functions.  In
   >a message-sending system, programs that accept messages generally
   >don't accept functions and vice-versa; you end up writing
   >	#'(lambda (x) (send x 'foo))
   >alot.

   I can't say for sure, but I don't think this is true unless you spend
   a lot of time writing named helper functions in CommonLisp.  I just
   scanned through my Smalltalk sources and couldn't find any such
   trivial lambda expressions (blocks, in Smalltalk parlance).  You
   almost always want to do more than just send a simple message in such
   a case.  So you either have to write a special purpose method/generic
   function, or you use a lambda expression anyway.

The reason Smalltalk doesn't need these trivial things is that in
Smalltalk, all function calls have one uniform syntax, namely
Smalltalk message passing.  The problem with Old Flavors (which used
"send") is that the language now had two distinct syntaxes for
function calls: regular function calls, and "send".  The trivial
lambda expression is a way of converting one to the other, sort of
like a 120v-to-240v converter, inelegantly stuck in the middle of
things.  In New Flavors and CLOS, "send" is eliminated, and everything
uses the same syntax again, which gets rid of the need for silly
conversions.

In our experience, it was not really true that "You almost always want
to do more than just send a simple message"; we often ran into just
such cases.  For example, you might have an array of function objects,
and want to do (funcall (aref the-array n) arg1 arg2), and some of the
elements were implemented as methods on the class of arg1, but a few
were not.  It was a real pain in the neck.  We converted large bodies of
code to use New Flavors and always liked the results.

pallas@Neon.Stanford.EDU (Joe Pallas) (09/18/89)

In article <1989Sep16.221107.22750@odi.com> dlw@odi.com (Dan Weinreb) writes:

>What causes the trouble for
>CLOS is the addition of the multimethod concept, and all the changes
>that it implies: there is no such thing as a "method of a class" in
>CLOS, because one function might be involved with more than one class
>at the same time.

Yes, I should have been more precise: it was specifically the multiple
dispatch feature of generic functions in CLOS that I meant.  One could
imagine, I suppose, having multimethods be "of" the classes of all the
discriminant arguments, but that would lead to the kind of
entanglement of different classes that OOP is praised for avoiding.

The whole notion of function dispatch based on arbitrary constraints
seems potentially very powerful, but it is arguably something entirely
different from object-oriented programming.

joe

dlw@odi.com (Dan Weinreb) (09/18/89)

In article <29573@news.Think.COM> barmar@think.COM (Barry Margolin) writes:

	   Flavors and CLOS permit methods to access any of the object's
   instance variables directly, even instance variables defined by a
   sibling class.

No, Flavors does not.  (It has the feature called "gettable" in old
Flavors and "readable" in New Flavors that allows you to give
outsiders access to instance variables, but they are off by default.)

barmar@think.COM (Barry Margolin) (09/19/89)

In article <1989Sep18.015805.9592@odi.com> dlw@odi.com writes:
>In article <29573@news.Think.COM> barmar@think.COM (Barry Margolin) writes:
>
>	   Flavors and CLOS permit methods to access any of the object's
>   instance variables directly, even instance variables defined by a
>   sibling class.
>
>No, Flavors does not.  (It has the feature called "gettable" in old
>Flavors and "readable" in New Flavors that allows you to give
>outsiders access to instance variables, but they are off by default.)

I just wrote the following code on my Lispm and it worked fine.  FOO
and BAR are sibling flavors in FOOBAR, and FOO's methods can access
BAR's instance variable A.

(defflavor foo () ()
  (:required-instance-variables a))
(defmethod (frob foo) ()
  a)

(defflavor bar (a) ())
(defmethod (set-a bar) (new-a)
  (setq a new-a))

(defflavor foobar () (foo bar))

(setq it (make-instance 'foobar))

(set-a it 3)

(frob it) => 3

Barry Margolin
Thinking Machines Corp.

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

lou@bearcat.rutgers.edu (Lou Steinberg) (09/20/89)

In article <11815@polya.Stanford.EDU> pallas@Neon.Stanford.EDU (Joe Pallas) writes:

> [...]  But I would argue that it is precisely because of
> generic functions that CLOS CANNOT have protection.  To have
> protection, you must have a clearly defined protection boundary.
> Method invocations establish such a boundary, but generic functions do
> not.

Not quite.  In standard OOP, the traditional protection boundary is
around methods belonging to a given class.  (OOP languages differ as
to whether that means just "class C" or "C and its descendants".)
With CLOS, multi-methods can "belong" to a set of unrelated classes -
one for each argument - so indeed the traditional protection boundary
cannot be used when you have such methods.  That does not mean that
"CLOS CANNOT have protection".  You just have to define other
boundaries.  E.g. you could give a list of classes, and say that a
method is within this protection boundary if all the classes it
specializes on are in this list.

If you want to, you can just put one class in each list, and get the
effect of the tradtional OOP boundaries.  But suppose there is a case
where to do some operation on two objects you want to be able to
access some aspect of the internals of each object.  In traditional
OOP languages, this operation is a method of one of the objects, so
the interface of the other object has to export enough access to allow
a method of the first object to do the access the operation needs.
However, once exported this access is available to the world.  In CLOS
with my hypothetical protection mechanism, you need only create a
boundary with the two classes and put the method for this operation in
that boundary.  In such a case, a strict association of a protection
boundary with a class would force you to subvert the protection
mechanism, and leaves you with less protection than you would have had
if protection boundaries were more flexible.

(Note that the issue here is not one of send vs function-call syntax,
but one of multi-methods versus methods specialized on only one
argument.)
-- 
					Lou Steinberg

uucp:   {pretty much any major site}!rutgers!aramis.rutgers.edu!lou 
arpa:   lou@aramis.rutgers.edu

dlw@odi.com (Dan Weinreb) (09/20/89)

In article <29625@news.Think.COM> barmar@think.COM (Barry Margolin) writes:


   I just wrote the following code on my Lispm and it worked fine.  FOO
   and BAR are sibling flavors in FOOBAR, and FOO's methods can access
   BAR's instance variable A.

Well, of course it can; you explicitly requested it by saying
"(:required-instance-variables a)", which is like what C++ calls a
"friend" declaration.  Your house won't be very secure if you leave
the key out front with a huge day-glo "Here's The Key!!!" sign,
either.  Like any good encapsulation system, Flavors lets you
control the encapsulation selectively when you need to.

rcp@milano.sw.mcc.com (Rob Pettengill) (09/20/89)

One reason for not including encapsulation directly in CLOS, is that
Common Lisp already has a generic encapsulation mechanism: the package
system.  

;rob 

  Robert C. Pettengill, MCC Software Technology Program
  P. O. Box 200195, Austin, Texas  78720
  ARPA:  rcp@mcc.com            PHONE:  (512) 338-3533
  UUCP:  ..!cs.utexas.edu!milano!rcp

-- 
  Robert C. Pettengill, MCC Software Technology Program
  P. O. Box 200195, Austin, Texas  78720
  ARPA:  rcp@mcc.com            PHONE:  (512) 338-3533
  UUCP:  ..!cs.utexas.edu!milano!rcp

jeff@aiai.uucp (Jeff Dalton) (09/27/89)

In article <71186@linus.UUCP> john@mitre.org writes:
>Wayne Folta writes:
>> ... I have been reading a book which implies that CLOS can be argued
>> to not be OO:
>>
>>"Because CLOS makes no effort at protection or encapsulation, some might
>>find that it lacks the most essential thing for an object-oriented system."
>>
>>"Generic functions are a step away from the concept of active data and back
>>toward the conventional division between passive data and active process."

I haven't seen Wayne Folta's message (or at least not yet).  Can
anyone tell me what book it was?

To me, this looks like a case of Lisp's pragmatic approach running up
against a (from this pragmatic point of view) more ideological approach.
(Like most generalizations, though, this one isn't quite true.  There
was a debate within the Lisp community about the importance of
encapsulation and the significance of 'send' as opposed to generic
functions.)

In what I call the pragmatic view, the difference between

   (<operation name> <instance> <arg>*)

and

   (send <instance> <operation-name> <arg>*)

is mostly a matter of which kind of syntactic sugar you prefer,
not a deep difference in how you think.  CLOS uses the function
call syntax in order to be more consistent with the rest of
Lisp.

Then, once you adopt the function call syntax, the generalization
to multi-methods seems fairly natural.  It does make it harder to
think of the objects as being in control, but is that enough to
make CLOS not be object-oriented?

Many people have tried to define the "essential" properties of object-
oriented programming.  I don't think there's anything wrong with trying
to do that, because it might further our understanding of OOP, but I
don't think it's very useful to be dogmatic about the results.  Not
only that, some people include as essential some things that don't seem
essential at all.

For example, I think T has an object-oriented component (see the
Adams and Rees paper in the last Lisp conference) even though it
doesn't have classes, uses generic functions instead of 'send',
and uses something more like delegation that inheritance.

As another example, I don't think C++ is not object-oriented even
though member functions are just functions.  That is, a C++ object
contains methods rather than acting as an interpreter for messages.

-- Jeff