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