Reasons for rejecting CLOS 
Author Message
 Reasons for rejecting CLOS

: On Sun, 9 May 1999 18:05:26 GMT,

: ...
: Bill> CLOS evolved from systems which imposed
: Bill> that asymmetry, it dropped that asymmetry in order to gain some very
: Bill> useful generality (multiple inheritance), and (as above) it retains
:                                   ^^^^^^^^^^^
:               (You mean 'multiple argument dispatch' here? )

: Bill> the ability to describe algorithms which have that asymmetry. So I
: Bill> just don't see the problem -- it honestly is a feature, not a bug.

: Bill> (Others have pointed out that Bjarne Stroustrup himself has said that
: Bill> multiple inheritance is very nice. I'll also point out that Scott

:                ^^^^^^^^^^^ (multiple argument dispatch ?)

: Bill> Meyers, in _Effective C++_ or _More Effective C++_, I forget which,
: Bill> also spends a lot of time showing how to do some of this in C++.  You
: Bill> write you could "easily" specialize on the other variables, but I
: Bill> actually wouldn't characterize it as all that easy. It's obviously
: Bill> doable, but it's also tedious and messy.)

: --
: The mail transport agent is not liable for any coffee stains in this message
: -----------------------------------------------------------------------------

: +44 (0)1223 49 4639                 | Wellcome Trust Genome Campus, Hinxton
: +44 (0)1223 49 4468 (fax)           | Cambridgeshire CB10 1SD,  GREAT BRITAIN
: PGP fingerprint: E1 03 BF 80 94 61 B6 FC  50 3D 1F 64 40 75 FB 53

Yes, of course, thank you. (Argh! where was my brain??)

  Bill Newman



Fri, 26 Oct 2001 03:00:00 GMT  
 Reasons for rejecting CLOS

Quote:

> The problem was that I had to go to considerable trouble to convince
> the compiler (CMUCL) that it could safely use fixed-width arithmetic
> in the termination tests for loops.

>   (DO ((I 0 (1+ I)))
>       ((>= I UPPER-LIMIT))
>     (DO-SOMETHING I))

(declaim (optimize (speed 3)))

(defun zog (upper-limit)
  (declare (fixnum upper-limit))
  (do ((i 0 (1+ i)))
      ((>= i upper-limit))
    (declare (fixnum i))
    (prin1 i)))

compiles to the following on my box.

[First of all, some function-entry stuff.]

05798B10:       .ENTRY ZOG(upper-limit)      ; (FUNCTION (FIXNUM) NULL)
      28:       POP   DWORD PTR [EBP-8]
      2B:       LEA   ESP, [EBP-32]

      2E:       CMP   ECX, 4
      31:       JNE   L2
      33:       TEST  EDX, 3
      39:       JNE   L3
      3B:       MOV   [EBP-12], EDX

[Now, set I to 0.]

      3E:       XOR   EBX, EBX               ; No-arg-parsing entry point

[And go to the termination test.]

      40:       JMP   L1

[I think the next bit is the function-call sequence implementing (prin1 i).]

      42: L0:   MOV   [EBP-16], EBX
      45:       MOV   ESI, ESP
      47:       SUB   ESP, 12
      4A:       MOV   EDX, EBX
      4C:       MOV   EAX, [#x5798B0C]
      52:       MOV   ECX, 4
      57:       MOV   [ESI-4], EBP
      5A:       MOV   DWORD PTR [ESI-8], 91851639
      61:       MOV   EBP, ESI
      63:       PUSH  91851639
      68:       JMP   DWORD PTR [EAX+5]
      6B:       NOP

      6C:       NOP
      6D:       NOP
      6E:       NOP
      6F:       NOP

;;; [12] (PRIN1 I)

      70:       .LRA
      74:       NOP
      75:       NOP
      76:       NOP
      77:       MOV   ESP, EBX
      79:       MOV   EBX, [EBP-16]

[So, we've finished calling PRIN1. Now update the loop variable.]

      7C:       ADD   EBX, 4

[Here's the termination test.]

      7F: L1:   CMP   EBX, [EBP-12]
      82:       JL    L0

[That was pretty painless. What follows is function-exit stuff.]

      84:       MOV   EDX, 83886091
      89:       MOV   EBX, [EBP-8]
      8C:       MOV   ECX, [EBP-4]
      8F:       ADD   EBX, 2
      92:       MOV   ESP, EBP
      94:       MOV   EBP, ECX
      96:       JMP   EBX

[And here's what happens if ZOG is called improperly.]

      98: L2:   BREAK 10                     ; Error trap
      9A:       BYTE  #x02
      9B:       BYTE  #x19                   ; INVALID-ARGUMENT-COUNT-ERROR
      9C:       BYTE  #x4D                   ; ECX
      9D: L3:   BREAK 10                     ; Error trap
      9F:       BYTE  #x02
      A0:       BYTE  #x0A                   ; OBJECT-NOT-FIXNUM-ERROR
      A1:       BYTE  #x8E                   ; EDX

Seems to be treating I as a fixnum very happily indeed.

--
Gareth McCaughan       Dept. of Pure Mathematics & Mathematical Statistics,



Fri, 26 Oct 2001 03:00:00 GMT  
 Reasons for rejecting CLOS
: >
: >
: > Sometimes "which object is being operated upon?" is not a meaningful
: > question. In my experience, it's not a meaningful question in any code
: > which can be written in a "functional", side-effect-free style.

: OK, I see your point here, but you yourself dictate it as a "functional"
: style, not an object-oriented one.  The case you are referring to is one
: where you have written a "function" that takes some arguments, does some
: processing, and returns value with no side effects.  This is a useful
: functionality to have, but is object-oriented?

: > Which object is being operated on in TYPE-INTERSECTION or TYPE-UNION?

: Both are; these functions should be implemented as functions, not as
: methods on an object.

: > Which is better, overloading the first argument or the second?

: Overload neither; objects and classes are self-describing, so you can
: easily write in a cond or case statement to perform special operations
: in these cases.

: > Which of the methods below don't you like?

: >   (DEFMETHOD TYPE-SUBTYPEP ((X MEMBER-TYPE) (Y TYPE))
: >     (EVERY (LAMBDA (MEMBER) (TYPE-SUBTYPEP MEMBER Y)) (MEMBERS X)))
: >   (DEFMETHOD TYPE-INTERSECTION ((X TYPE) (Y MEMBER-TYPE))
: >     (MAKE-INSTANCE 'MEMBER-TYPE
: >                    :MEMBERS
: >                    (REMOVE-IF (LAMBDA (MEMBER)
: >                                 (NOT (OBJECT-HAS-TYPE? MEMBER X)))
: >                               (MEMBERS X))))
: >   (DEFMETHOD TYPE-INTERSECTION ((X MEMBER-TYPE) (Y MEMBER-TYPE))
: >     ..)
: >   (DEFMETHOD TYPE-UNION ((X MEMBER-TYPE) (Y MEMBER-TYPE))
: >     ..)

: These are all poor methods, they are in every way functions.

: > When you have side effects, then often it does make sense to talk
: > about what's *the* object and what's not. For example, if we say
: >
: >   (DEFGENERIC LEARN ((KB KNOWLEDGE-BASE) (F FACT) (TL TIME-LIMIT)))
: >   (DEFGENERIC UNLEARN ((KB KNOWLEDGE-BASE) (F FACT) (TL TIME-LIMIT)))

: And this is a case where you are working with object-orientation because
: you are using a code/data abstraction.  The previous examples took
: several objects, and probably would have used methods to get the
: information they needed from it, while these specifically operate upon
: one object.

: > then the KNOWLEDGE-BASE is likely to be the object.  However, (1)
: > multiple inheritance can still be useful (consider different classes
: > of FACTs..) and (2) I fail to see what CLOS loses by using general
: > syntax.  Why is
: >
: >   kb->learn(f,tl)
: >
: > better than
: >
: >   (LEARN KB F TL)?

: Because (LEARN KB F TL) rapidly loses its meaning when you are not
: careful.  I do not know the intimates of CLOS, but say for example that
: I write a method like:

: (defmethod learn (kb (f my-fact) tl) ... )

: This method specialises on the second object, but not the first.  While
: initially this seems like it may be a feature because we can now
: intercept any calls to "learn" our special type of fact.  On the other
: hand, (LEARN KB F TL) is no longer equivalent to KB->(LEARN F TL), and
: maybe instead decides to have side effects on F instead of KB (we don't
: know, and there is no way to find out) while still using the same
: generic function.  The other approach means that we do know, given an
: object's API, whether it is that object or another that is most likely
: to be operated on.  Rather than an inplementation or flexibility issue,
: it is about readability and psychology.

I believe that the way to deal with this is to have the generic
function definition specify carefully what it does, including which
objects can have side-effects or access to private object state. In my
experience, that works well, but of course there are lots of
programming cultures, lots of project sizes, and lots of project
timescales, so YMMV.

As to whether the object system requires side-effects to be limited
to a single object, I still believe that most side-effect-ful
operations tend to have a single preferred object, but I don't
think they *all* do. Consider

  (DEFGENERIC SYNCHRONIZE! ((C1 CACHE) (C2 CACHE)))
  (DEFGENERIC MERGE! ((C1 COLLECTION) (C2 COLLECTION))
  (DEFGENERIC TRANSFER! ((TO ACCOUNT) (FROM ACCOUNT) SOMETHING)

Sometimes you can rewrite these as mutations on individual objects,
e.g. TRANSFER! might be broken into CREDIT! and DEBIT! if the
synchronization requirements weren't too complex.  But consider also..

  'Having one argument of an operation (the one designating
  the "object") special can lead to contorted designs. When
  several arguments are best treated equally, an operation is
  best represented as a nonmember function.'
    -- Stroustrup, _The C++ Programming Language_, 3d edition, p. 732

: > I do see what I think are problems with CLOS, especially the large
: > number of levels of indirection it imposes on the implementation even
: > in the simplest cases. I can also understand other people who think
: > weak static type checking or lack of enforced information hiding
: > are problems. But criticizing CLOS for not imposing an asymmetric
: > "this argument is the object, the others are along for the ride"
: > semantics seems fairly silly.  

: Perhaps my criticism there was tagged on for the ride, in response to
: someone's trivialisation of it.  It is my personal belief that syntax
: has more power over a programmer than anything else in a language, at
: many levels.  Syntax decides how a programmer will use things,
: regardless of how they CAN be used.  A syntax that places emphasis on a
: particular object means that programmers will write in a way that uses
: one object.  A syntax that places no emphasis leads to programs that use
: any number of the objects, possibly in any number of ways.  The failure
: to hide data means that programmers feel free to modify the data from
: anywhere in their code, regardless of good practices etc.

There are two issues here. On the first issue, I still think the
symmetry of CLOS is a feature, not a bug. Multiple dispatch (not
multiple *inheritance* as I repeatedly and mistakenly wrote earlier..)
is nice.

On the second issue, I already said above that I can understand
criticism of CLOS's lack of enforced information hiding.  I'm
personally happy with that aspect of CLOS, but I also think it's a
case of "horses for courses": I'm not surprised that other people find
it distasteful, or inappropriate in some circumstances.

: > CLOS evolved from systems which imposed
: > that asymmetry, it dropped that asymmetry in order to gain some very
: > useful generality (multiple inheritance), and (as above) it retains
: > the ability to describe algorithms which have that asymmetry. So I
: > just don't see the problem -- it honestly is a feature, not a bug.

: I fail to see how multiple inheritance is affected by the syntax in this
: case.. perhaps I use the word in a different way than you do?

I screwed up and wrote multiple inheritance when I meant multiple
dispatch. Sorry..

: > (Others have pointed out that Bjarne Stroustrup himself has said that
: > multiple inheritance is very nice. I'll also point out that Scott
: > Meyers, in _Effective C++_ or _More Effective C++_, I forget which,
: > also spends a lot of time showing how to do some of this in C++.  You
: > write you could "easily" specialize on the other variables, but I
: > actually wouldn't characterize it as all that easy. It's obviously
: > doable, but it's also tedious and messy.)

: You would specialise on the other variables exactly as CLOS does, I
: don't see how it could possibly be any more messy than that.  You would
: not even require a code change.

Not for the problems that multiple dispatch (not multiple inheritance,
argh!)  is usually used to solve. In particular, you usually want
inheritance to work on each of the dispatched arguments, and once you
commit to that, you do end up writing some messy code (unless you're
using CLOS or Dylan, of course:-).

Multiple dispatch is not the same thing as multiple inheritance, but
it's like it, in that when you want it, you really want it, and when
you don't, it's hard to see why anyone would.:-|

  Bill Newman



Fri, 26 Oct 2001 03:00:00 GMT  
 Reasons for rejecting CLOS

Quote:

> I am beginning to suspect that I am inspecting the problem with a less
> advanced perspective.. sort of a C++ bias on the whole issue, because I
> see the issue as divided between two seperate systems: functional code
> and object-oriented stuff.  CLOS I see conbines the two into one system,
> where a method is "shared" between the objects it operates on.

This is a good first step.

CLOS does use a different object model from C++.  I suspect that a lot
of (perhaps needless) confusion results from this not being clear.  Some
major points of difference:

   C++:  Objects encapsulate both data and functions
  CLOS:  Objects encapsulate data only

   C++:  Methods belong to objects
  CLOS:  Methods belong to generic functions.

This latter point is a big change in the view of a programming system.

Instead of organizing methods by the class to which their first argument
belongs, all methods are organized by the function that they implement.
This is a very different way of thinking about organizing code.  It
focuses on the operation as the organizational backbone, rather than on
the object.

There are a couple of interesting effects of this decision.

It makes less of a distinction between built-in objects like
DOUBLE-FLOAT or STRING and user defined objects.  You can easily define
new functions over combinations of built-in types.

There is no syntactic distinction between generic function (method)
calls and normal function calls.  Since they operate in much the same
way, it means that one could in principle change functions into generic
functions (and vice versa) without needing to change the calling code --
something you can't do for static versus non static member functions in
C++ or Java.

It makes it easy to add your own processing to a system that contains
objects provided by the system or by other software packages.  If you
received a C++ object library and wanted to add methods to those
objects, you would be forced to subclass the objects in order to do it.
In CLOS, you can define a generic function with methods that are
dispatched on existing objects defined by others, without needing to
disturb those objects or subclass them.

Historical note:  An earlier OO system for Lisp (FLAVORS), was very much
   based on the message passing paradigm.  It even used SEND to send
   messages to a single distinguished object.

I wonder if Kent Pitman or someone else would care to comment on some of
the background that went into deciding to use the CLOS/generic function
model rather than the object/message passing model for the Common Lisp
object system?

--



Fri, 26 Oct 2001 03:00:00 GMT  
 Reasons for rejecting CLOS

Quote:


>> Sometimes "which object is being operated upon?" is not a meaningful
>> question. In my experience, it's not a meaningful question in any code
>> which can be written in a "functional", side-effect-free style.

> OK, I see your point here, but you yourself dictate it as a "functional"
> style, not an object-oriented one.  The case you are referring to is one
> where you have written a "function" that takes some arguments, does some
> processing, and returns value with no side effects.  This is a useful
> functionality to have, but is object-oriented?

I don't see why it shouldn't be. It's purely a matter of terminology,
anyway. Whether or not you want to use the term "OO" when the "objects"
being worked on are immutable, it's clear that one can benefit from
information hiding, polymorphism and inheritance in that situation.

Quote:
>> Which object is being operated on in TYPE-INTERSECTION or TYPE-UNION?

> Both are; these functions should be implemented as functions, not as
> methods on an object.

Why? Because they don't have side effects? Surely not; I can't believe
you would suggest that all accessors should be "functions, not methods"
(and presumably, as per your next comment, implemented using a COND or
a CASE!). Because the objects they're working on are immutable? So,
would your opinion suddenly reverse if the type system changed so that
the contents of these TYPE objects could legitimately be altered after
their creation? Because they can make new objects? (Why should that
make a difference?)

None of these reasons seems to me sufficient to say Thou Shalt Not
Implement These As Methods; so what *is* the problem? Is it, in fact,
simply that they involve multiple dispatch and you don't believe in
multiple dispatch?

Quote:
>> Which is better, overloading the first argument or the second?

> Overload neither; objects and classes are self-describing, so you can
> easily write in a cond or case statement to perform special operations
> in these cases.

Er, what? So much for object orientation...

Quote:
> > Which of the methods below don't you like?

> >   (DEFMETHOD TYPE-SUBTYPEP ((X MEMBER-TYPE) (Y TYPE))
> >     (EVERY (LAMBDA (MEMBER) (TYPE-SUBTYPEP MEMBER Y)) (MEMBERS X)))
> >   (DEFMETHOD TYPE-INTERSECTION ((X TYPE) (Y MEMBER-TYPE))
> >     (MAKE-INSTANCE 'MEMBER-TYPE
> >                    :MEMBERS
> >                    (REMOVE-IF (LAMBDA (MEMBER)
> >                                 (NOT (OBJECT-HAS-TYPE? MEMBER X)))
> >                               (MEMBERS X))))
> >   (DEFMETHOD TYPE-INTERSECTION ((X MEMBER-TYPE) (Y MEMBER-TYPE))
> >     ..)
> >   (DEFMETHOD TYPE-UNION ((X MEMBER-TYPE) (Y MEMBER-TYPE))
> >     ..)

> These are all poor methods, they are in every way functions.

How do you want them to be implemented, then? Something like this?

    (defun type-intersection (x y)
      (cond
        ((member-type-p x)
         ...)
        ((numeric-type-p x)
         ...)
        ((enumerated-type-p x)
         ...)
        ((structured-type-p x)
         ...)
        ...))

If you really think this is a good way to write code, then I don't
understand why you're interested in OO at all.

- Show quoted text -

Quote:
>> then the KNOWLEDGE-BASE is likely to be the object.  However, (1)
>> multiple inheritance can still be useful (consider different classes
>> of FACTs..) and (2) I fail to see what CLOS loses by using general
>> syntax.  Why is

>> kb->learn(f,tl)

>> better than

>> (LEARN KB F TL)?

> Because (LEARN KB F TL) rapidly loses its meaning when you are not
> careful.  I do not know the intimates of CLOS, but say for example that
> I write a method like:

> (defmethod learn (kb (f my-fact) tl) ... )

> This method specialises on the second object, but not the first.  While
> initially this seems like it may be a feature because we can now
> intercept any calls to "learn" our special type of fact.  On the other
> hand, (LEARN KB F TL) is no longer equivalent to KB->(LEARN F TL), and
> maybe instead decides to have side effects on F instead of KB (we don't
> know, and there is no way to find out) while still using the same
> generic function.  The other approach means that we do know, given an
> object's API, whether it is that object or another that is most likely
> to be operated on.  Rather than an inplementation or flexibility issue,
> it is about readability and psychology.

This is just an issue of style.

There is nothing to stop a method on one object side-effecting
another (even in systems that, unlike CLOS, place heavy restrictions
on access to objects other than via their methods). So even in C++
you have no sort of guarantee that saying |kb->learn(f,tl)| won't
side-effect f. Maybe the first thing the |learn| method does is to
say |f->mangle_self()|. You can certainly adopt a *convention* that
a method never side-effects its arguments...

... but then, in CLOS, you can also adopt a convention that a generic
function never side-effects any argument other than the first. If that
turns out to be helpful.

What's the problem here?

Quote:
> Perhaps my criticism there was tagged on for the ride, in response to
> someone's trivialisation of it.  It is my personal belief that syntax
> has more power over a programmer than anything else in a language, at
> many levels.  Syntax decides how a programmer will use things,
> regardless of how they CAN be used.  A syntax that places emphasis on a
> particular object means that programmers will write in a way that uses
> one object.  A syntax that places no emphasis leads to programs that use
> any number of the objects, possibly in any number of ways.  The failure
> to hide data means that programmers feel free to modify the data from
> anywhere in their code, regardless of good practices etc.

This reminds me very strongly of something Erik Naggum once said,
along the following lines. "Common Lisp doesn't enforce these
restrictions; you have to be polite. C++ does. The effect is that
decent people use Common Lisp, whereas thieves and bums use C++".
I think he exaggerated to make his point, but it is a point even so.

If you work in Common Lisp, you can agree that no one will make any
direct access to an object outside methods specialised on that object
as first argument; or that you won't use multiple dispatch at all;
or whatever you like. If such an agreement is helpful to your project
but the people on it are incapable of sticking to it, then you certainly
have problems, but they aren't with CLOS.

Sleazy programmers have just as much opportunity for sleaze in C++ as
they have in CLOS.

#define private public
#include "FooClass.h"

:-) (I think this particular observation may also be Erik Naggum's.)

--
Gareth McCaughan       Dept. of Pure Mathematics & Mathematical Statistics,



Fri, 26 Oct 2001 03:00:00 GMT  
 Reasons for rejecting CLOS
: Dynamic compiler:
...
: CMUCL

No one seems to have mentioned what class CMUCL is in.

CMUCL lets you dumps its memory image to disk and reload it into a
fresh CMUCL, or you can just load FASL into a fresh CMUCL. It's rather
UNIX-centric, so the implementors might consider it a strange question
if you asked "but can you generate a real executable file": the answer
might be that

  #!/bin/sh
  /usr/bin/cmucl -core /usr/local/lib/myprog.core -eval "(startup)"

or

  #!/bin/sh
  /usr/bin/cmucl -eval '(load "/usr/local/lib/myprog/main.fasl")'

*is* a real executable file.

It works pretty well for me, anyway. The only potential disadvantage
would be startup overhead, and it's much than a second in both cases,
which is good enough for me.

  Bill Newman



Fri, 26 Oct 2001 03:00:00 GMT  
 Reasons for rejecting CLOS
    Bill> The problem was that I had to go to considerable trouble to convince
    Bill> the compiler (CMUCL) that it could safely use fixed-width arithmetic
    Bill> in the termination tests for loops.

    Bill>   (DO ((I 0 (1+ I)))
    Bill>       ((>= I UPPER-LIMIT))
    Bill>     (DO-SOMETHING I))

    Bill> No matter what interval you tell CMUCL that I is in, it can't
    Bill> prove to itself that (1+ I) is in the same interval.

Well, if it could, then the compiler would be broken because (1+ I)
can't be in the same interval as I, because you've added 1 to it. :-)
(Assuming I is an integer.)

CMUCL can't determine the type because the above macroexpands to
something that has a (setq i ...) in it.  This is a known deficiency
in the compiler that I wish I knew enough to remove, since CMUCL does
a good job if you don't use setq.

    Bill> Mostly these days I declare indices like this to be
    Bill> of some restricted type like FIXNUM/2

    Bill>   (DEFTYPE FIXNUM/2 () `(MOD ,(FLOOR MOST-POSITIVE-FIXNUM 2))).

    Bill> That way, even if CMUCL isn't sure that I hasn't left the interval, at
    Bill> least it knows it hasn't become a bignum. But it took me a lot of

You can always add (declare (fixnum i) (optimize (speed 3) (safety 0))
to the loop.  Just don't lie about I being a fixnum! :-)

Ray



Fri, 26 Oct 2001 03:00:00 GMT  
 Reasons for rejecting CLOS


Quote:
> I think a better name for this notion that Smalltalk and Java and
> other systems have of everything being about messages to single
> objects would be "object-centric" or even "self-centric".

Or, better yet, "self-centered".

-- Harley



Fri, 26 Oct 2001 03:00:00 GMT  
 Reasons for rejecting CLOS

Quote:


> > Huh?  There are more than two Common Lisp vendors, and Allegro only
> > started producing executables under Windows late last year when they
> > released Allegro 5.0.  Allegro 3.0.2 which I have been using at school
> > for the last term does not.  I suspect that it is not alone in this
> > bracket.

> I don't know if it (3.x) produces executables as such but it certainly
> produces things that are standalone enough that they fit on a floppy
> including all the runtime support & GUI stuff.  Until recently I
> worked for some people who did exactly that: we had a paper at last
> year's LUGM about exactly this.

Yeah, you generate an image file and then pass that as an argument to
the LISP.EXE in a shortcut, AFAIK.

You lose the power to accept command-line parameters and similar, but
thats not a huge loss.  Unfortunately the interface to do so is a little
difficult to use properly, there are a few options and so forth.. I
imagine you could figure it out and use it fairly easily after some
practice and research.

CU
Dobes



Fri, 26 Oct 2001 03:00:00 GMT  
 
 [ 350 post ]  Go to page: [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] [23] [24]

 Relevant Pages 
 

 
Powered by phpBB® Forum Software