From: Robert Strandh
Subject: Q: modularity problem with CLOS
Date: 
Message-ID: <85adnubtrb.fsf@eta.emi.u-bordeaux.fr>
Hello,

I have a module, let's call it `birds', that contain a number of CLOS
classes that represent various aspects of birds.  Then I need two more
modules, lets call them `numbered birds' and `extended birds'.  Both
these last modules need to extend the classes in the `birds' module in
a way transparent to the `birds' module.  In addition, I don't want
the `numbered birds' module and the `extended birds' module to know of
each other, though both can know about the `birds' module.

When (say) a bird is created by (make-instance 'bird) in the `bird'
module, I really want for an extended and numbered bird to be
created.  If I had had only the `bird' and the `extended bird'
modules, I could have done something like this:

---- in birds.lisp ----

(defclass bird (...) (...))

---- in extended-birds.lisp ----

(defclass ebird (bird) (...))

(defmethod initialize-instance :after ((b bird) &rest args)
  (change-class b 'ebird))

But this method requires the `extended birds' module to know the name
of the base class (bird) which is OK if I only have these two modules,
but no longer OK when I add the `numberd birds' module.  I kind of
would like to obtain the effect of:

---- in birds.lisp ----

(defclass bird (...) (...))

---- in numbered-birds.lisp ----

(defclass nbird (bird) (...))

(defmethod initialize-instance :after ((b bird) &rest args)
  (change-class b 'nbird))

---- in extended-birds.lisp ----

(defclass ebird (nbird) (...))

(defmethod initialize-instance :after ((b nbird) &rest args)
  (change-class b 'ebird))

except that I don't want to mention `nbird' in the `extended birds'
module.

Any ideas?
-- 
Robert Strandh

From: Thomas F. Burdick
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <xcvadnuioqk.fsf@conquest.OCF.Berkeley.EDU>
Robert Strandh <·······@labri.u-bordeaux.fr> writes:

> Hello,
> 
> I have a module, let's call it `birds', that contain a number of CLOS
> classes that represent various aspects of birds.  Then I need two more
> modules, lets call them `numbered birds' and `extended birds'.  Both
> these last modules need to extend the classes in the `birds' module in
> a way transparent to the `birds' module.  In addition, I don't want
> the `numbered birds' module and the `extended birds' module to know of
> each other, though both can know about the `birds' module.
> 
> When (say) a bird is created by (make-instance 'bird) in the `bird'
> module, I really want for an extended and numbered bird to be
> created.  If I had had only the `bird' and the `extended bird'
> modules, I could have done something like this:

I'd think for most uses, you could use method combinations to achieve
what you want.  However, if you really need/want the classes to be
different, here's how I've done it:

Make ebird a mixin class.  Write a function that will take a class and
a mixin class, and will return a class that inherits from both, using
MOP:ENSURE-CLASS.  When trying to create an instance of bird, change
the instances class to this dynamically created class.  Do the same
for nbird.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Robert Strandh
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <85fzxlix5k.fsf@eta.emi.u-bordeaux.fr>
···@conquest.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> Robert Strandh <·······@labri.u-bordeaux.fr> writes:
> 
> > Hello,
> > 
> > I have a module, let's call it `birds', that contain a number of CLOS
> > classes that represent various aspects of birds.  Then I need two more
> > modules, lets call them `numbered birds' and `extended birds'.  Both
> > these last modules need to extend the classes in the `birds' module in
> > a way transparent to the `birds' module.  In addition, I don't want
> > the `numbered birds' module and the `extended birds' module to know of
> > each other, though both can know about the `birds' module.
> > 
> > When (say) a bird is created by (make-instance 'bird) in the `bird'
> > module, I really want for an extended and numbered bird to be
> > created.  If I had had only the `bird' and the `extended bird'
> > modules, I could have done something like this:
> 
> I'd think for most uses, you could use method combinations to achieve
> what you want.  However, if you really need/want the classes to be
> different, here's how I've done it:

I neither need nor want the classes to be different.  It is just that
the `bird' module defines a class that does not have all the slots I
need.  I would like to try to avoid having the `birds' module know
about the additional slots that are needed by the `numbered birds' and
`extended birds' modules.  I am afraid I don't see how to do that with
method combinations.

> Make ebird a mixin class.  Write a function that will take a class and
> a mixin class, and will return a class that inherits from both, using
> MOP:ENSURE-CLASS.  When trying to create an instance of bird, change
> the instances class to this dynamically created class.  Do the same
> for nbird.

I had thought about something like that.  Would I compute the new
class at each attempt to instantiate the `bird' class, or would I do
it at compile time by (say) creating an instance of `bird', figure out
what class it has, and create a new class which includes the mixin?

Thanks for helping out,
-- 
Robert Strandh
From: Thomas F. Burdick
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <xcv7kix1fdx.fsf@conquest.OCF.Berkeley.EDU>
Robert Strandh <·······@labri.u-bordeaux.fr> writes:

> I neither need nor want the classes to be different.  It is just that
> the `bird' module defines a class that does not have all the slots I
> need.  I would like to try to avoid having the `birds' module know
> about the additional slots that are needed by the `numbered birds' and
> `extended birds' modules.  I am afraid I don't see how to do that with
> method combinations.

Well, for some simple things, using :around, :before, and :after
methods that are closures is sometimes sufficient (and not too nasty
of a reinvention of classes), and don't require the defining code, or
the consuming code to know about the changes.  This sounds like what I
meant by "need".

> > Make ebird a mixin class.  Write a function that will take a class and
> > a mixin class, and will return a class that inherits from both, using
> > MOP:ENSURE-CLASS.  When trying to create an instance of bird, change
> > the instances class to this dynamically created class.  Do the same
> > for nbird.
> 
> I had thought about something like that.  Would I compute the new
> class at each attempt to instantiate the `bird' class, or would I do
> it at compile time by (say) creating an instance of `bird', figure out
> what class it has, and create a new class which includes the mixin?

I have a memoized function DYNAMICALLY-MIX-IN that I use for this, so
the class is only computed the first time, after that, it's just an
EQUALP hash table lookup.  You can then do it at runtime.  I felt a
little queasy with the idea of CHANGE-CLASS being invoked on every
object creation [*].  After reading later follow-ups, I have a couple
ideas on how to do this.  Both involve significantly breaking
modularity, but that's exactly what you're trying to do, so...

1 - You might do it with an :around method on MAKE-INSTANCE.  Have
EBIRD and NBIRD register themselves as needed mixins for BIRD, and
have the MAKE-INSTANCE method create a class that mixes those in with
BIRD, then actually makes an instance of that class.

2 - Where you define EBIRD and NBIRD, you could have them add
themselves to BIRD's superclass list.

I like (2) the best, and it basically does what you want: it lets you
modify the definition of BIRD without the original definition knowing
about it, and without having to know what other modifications are
being made.  Its main problem is that if you redefine BIRD, you'll
need to re-insert NBIRD and EBIRD into its superclass list.  The
problem with (1) is:

  (eq (find-class 'bird) (class-of (make-instance 'bird)))
    => NIL

Which is gross, and could potentially lead to problems, depending on
how you program.

[*] I personally don't have anything against CHANGE-CLASS, and think
that it's really useful.  I don't use it often, but when I do, it's so
much more elegant than any other alternative.  Unfortunately, it can
be painfully inefficient -- one use I'd made of it was a program that
walks a graph, discovering information about it, and annotating the
nodes with that new information.  My first solution used CHANGE-CLASS
and DYNAMICALLY-MIX-IN to make some of those annotations.  These were
for properties that were really best represented by types, and that
let me dispatch off of the type of the object for these purposes.
Unfortunately, although the resulting code was lovely, it was way too
inefficient, and I had to rewrite it.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Rahul Jain
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <87hehzpzrx.fsf@localhost.localdomain>
···@conquest.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> 2 - Where you define EBIRD and NBIRD, you could have them add
> themselves to BIRD's superclass list.
> 
> I like (2) the best, and it basically does what you want: it lets you
> modify the definition of BIRD without the original definition knowing
> about it, and without having to know what other modifications are
> being made.  Its main problem is that if you redefine BIRD, you'll
> need to re-insert NBIRD and EBIRD into its superclass list.  The
> problem with (1) is:
> 
>   (eq (find-class 'bird) (class-of (make-instance 'bird)))
>     => NIL
> 
> Which is gross, and could potentially lead to problems, depending on
> how you program.

I think some MOP solution involving adding a dependent on (find-class
'bird) and using reinitialize-instance on it when it is changed to
reinitialize the superclasses slot might work here.

-- 
-> -/                        - Rahul Jain -                        \- <-
-> -\  http://linux.rice.edu/~rahul -=-  ············@techie.com   /- <-
-> -X "Structure is nothing if it is all you got. Skeletons spook  X- <-
-> -/  people if [they] try to walk around on their own. I really  \- <-
-> -\  wonder why XML does not." -- Erik Naggum, comp.lang.lisp    /- <-
|--|--------|--------------|----|-------------|------|---------|-----|-|
   (c)1996-2002, All rights reserved. Disclaimer available upon request.
From: Christopher C. Stacy
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <uwuqypihk.fsf@dtpq.com>
>>>>> On 10 Aug 2002 17:47:36 +0200, Robert Strandh ("Robert") writes:

 Robert> Hello,
 Robert> I have a module, let's call it `birds', that contain a number of CLOS
 Robert> classes that represent various aspects of birds.  Then I need two more
 Robert> modules, lets call them `numbered birds' and `extended birds'.  Both
 Robert> these last modules need to extend the classes in the `birds' module in
 Robert> a way transparent to the `birds' module.  In addition, I don't want
 Robert> the `numbered birds' module and the `extended birds' module to know of
 Robert> each other, though both can know about the `birds' module.

 Robert> When (say) a bird is created by (make-instance 'bird) in the `bird'
 Robert> module, I really want for an extended and numbered bird to be
 Robert> created.  If I had had only the `bird' and the `extended bird'
 Robert> modules, I could have done something like this:

 Robert> (defclass bird (...) (...))

 Robert> (defclass ebird (bird) (...))

 Robert> (defmethod initialize-instance :after ((b bird) &rest args)
 Robert>   (change-class b 'ebird))

 Robert> But this method requires the `extended birds' module to know the name
 Robert> of the base class (bird) which is OK if I only have these two modules,
 Robert> but no longer OK when I add the `numberd birds' module.  I kind of
 Robert> would like to obtain the effect of:

 Robert> (defclass nbird (bird) (...))

 Robert> (defmethod initialize-instance :after ((b bird) &rest args)
 Robert>   (change-class b 'nbird))

 Robert> except that I don't want to mention `nbird' in the `extended
 Robert> birds' module.

If you intend to create an extended, numbered, bird, you would simply
define a new class that includes all three things (maybe transitively.
depending on what you want), and make an instance of that class:

(defclass bird () ())
(defclass ebird (bird) ())
(defclass nbird (bird) ())

(defclass e-n-bird (ebird nbird) ())
(make-instance 'e-n-bird)


CHANGE-CLASS is not a normal thing to do.  

It is for really weird applications where the objects 
actually change their nature, but are somehow the same
object.  For example, a catapillar turns into a butterfly.

Rather than making an instance of BIRD, and having it CHANGE-CLASS 
to a subclass like NBIRD or EBIRD, you want to simply make an 
instance of NBIRD or EBIRD in the first place.

Your example doesn't show why any of these classes need to know about
each other, so it's impossible to give other advice about how to lay
them out.  Here are some examples for thought:

(defclass numbered-mixin ()    ;All kinds of things could be numbered.
  ((tag-number :initform nil)))

(defclass flyer ()      ;Birds, bats, squirrels, etc.
   (speed))

(defclass singer () ...)

(defmethod flight-distance ((a flyer) time)
  (with-slots (speed) flyer
    (* speed tim)))

(defclass bird ()               ;What is a bird. 
  ((beak) 
   (feathers-color)))

(defclass penguin (bird))       ;Penguins don't fly.

; If my application had both birds that are numbered, and birds 
; that are not numbered (but might be), and I didn't have control 
; over the creation of the bird objects, then I might use TYPEP 
; and CHANGE-CLASS on them.  But more likely, I know that all
; birds are potentially numbered, so I define my class like:
;
; All sparrows in our study will be tagged and released.
; TAG-NUMBER can be:
;    * NIL prior to actually being caught
;    * A tag number
;    * A symbol (SICK, NOT-REALLY-A-SPARROW, etc.) 
;      indicating why no tag was applied

(defclass sparrow (bird singer flyer numbered-mixin))

(defmethod numbered-p ((x numbered-mixin))
  (with-slots (tag-number) x
    (numberp x)))


(defclass ebird (bird speech-understanding-mixin) ;Artificially enhanced bird.
  ...)

(defmethod do-the-laundry ((bird ebird) (washer (washing-machine)) ...)

; Fancy birds are birds that have been captured,
; tagged, run through some intelligence tests,
; and then taught to do my laundry.

(defclass fancy-bird (ebird numbered-mixin) ())

(setq tweety (make-instance 'sparrow))
(change-class tweety 'fancy-bird)

In this case, I'm adding a bunch of methods and stuff to the 
bird object.  I'm going to be passing that new object to a whole 
'nother set of programs, different from the plain numbered birds.

You'll notice that Keene gives CHANGE-CLASS  only one page in her book.

The example of CHANGE-CLASS in the CLHS is of a POSITION object 
that could be of subclass X-Y or subclass RHO-THETA.  There is an
UPDATE-INSTANCE-FOR-DIFFERENT-CLASS method for an X-Y object and
does the calculation for conversion to a RHO-THETA object.
But you have to ask: what would be the purpose of all that?
What underlying methods are still going to be useful?
One major set of instance variables (slots) are being dropped 
in favor of a new set of instance variables.  It probably indicates
that the object is moving from one way of doing things, 
to an entirely different way of doing things.

I can't think of any place I've ever needed to use it.  
It wasn't used in the Genera operating system, except as 
part of CLOS itself (eg. compatability kludge for Flavors).
A hairy expert system I worked on that designed wide-area networks
(dynamically changing around complex taxonomies of networks of
satellite and terrestrial links, tariffs, different kinds of 
switching nodes and performance models) didn't need to use it.

It might be the right thing, but you have to do some serious 
thinking ahead about how your program is designed, before 
jumping right at CHANGE-CLASS.

By the way, I would not generally use CLOS classes directly for a
knowledge representation system.  For one thing, you have to know
all the possible class combinations ahead of time.  CLOS is not rich 
enough to handle general taxonomy problems that are not pre-defined DAGs.

If objects have binary or relative properties, they would have a slot
indicating that (even if it's zero or NIL a lot of the time).  
More likely in most applications is that an object would be dynamically
entirely replaced with another object of a different class.

CHANGE-CLASS is a neat idea, but in practice it's not needed very
often.  The reason is that in the real world, things rarely change
from one kind of an object to another without also changing around 
the viewpoint or model for reasoning about them.

I think I need to go do my laundry now.
From: Robert Strandh
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <85d6spiwj0.fsf@eta.emi.u-bordeaux.fr>
······@dtpq.com (Christopher C. Stacy) writes:

> >>>>> On 10 Aug 2002 17:47:36 +0200, Robert Strandh ("Robert") writes:
> 
>  Robert> Hello,
>  Robert> I have a module, let's call it `birds', that contain a number of CLOS
>  Robert> classes that represent various aspects of birds.  Then I need two more
>  Robert> modules, lets call them `numbered birds' and `extended birds'.  Both
>  Robert> these last modules need to extend the classes in the `birds' module in
>  Robert> a way transparent to the `birds' module.  In addition, I don't want
>  Robert> the `numbered birds' module and the `extended birds' module to know of
>  Robert> each other, though both can know about the `birds' module.
> 
>  Robert> When (say) a bird is created by (make-instance 'bird) in the `bird'
>  Robert> module, I really want for an extended and numbered bird to be
>  Robert> created.
> 
> If you intend to create an extended, numbered, bird, you would simply
> define a new class that includes all three things (maybe transitively.
> depending on what you want), and make an instance of that class:
> 
> (defclass bird () ())
> (defclass ebird (bird) ())
> (defclass nbird (bird) ())
> 
> (defclass e-n-bird (ebird nbird) ())
> (make-instance 'e-n-bird)

Sure, but then I would need a place in the program that knows about
the existence of both ebirds and nbirds, which I would like to avoid.
But that might be a solution if nothing else works. 

> CHANGE-CLASS is not a normal thing to do.  
>
> It is for really weird applications where the objects 
> actually change their nature, but are somehow the same
> object.  For example, a catapillar turns into a butterfly.

OK, I didn't realize that.  

> (defclass sparrow (bird singer flyer numbered-mixin))

This is what I would like to avoid, namely that birds know that they
are numbered (so to speak).

> It might be the right thing, but you have to do some serious 
> thinking ahead about how your program is designed, before 
> jumping right at CHANGE-CLASS.

Yes, that's why I am asking here. 

> I think I need to go do my laundry now.

OK, thanks for all your hints, 
-- 
Robert Strandh
From: Joel Ray Holveck
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <y7c65yhelww.fsf@sindri.juniper.net>
> Sure, but then I would need a place in the program that knows about
> the existence of both ebirds and nbirds, which I would like to avoid.
> But that might be a solution if nothing else works.

Would whatever instantiates these birds know about them both?

And if not, how do you intend to take advantage of their extended and
numbered capabilities?

joelh
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3y9bdr6x9.fsf@cley.com>
* Joel Ray Holveck wrote:

> And if not, how do you intend to take advantage of their extended and
> numbered capabilities?

I think that you are right about his example, but there are other
cases where, for instance there is a bird protocol for which the
abstract class only defines part of the implementation, and for which
various mixins define the rest.  All the user needs to know is that
they have a class that implements the whole protocol, but there may be
several ways of getting such a class.

--tim
From: Barry Margolin
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <I2Q59.4$h1.1817@paloalto-snr1.gtei.net>
In article <···············@cley.com>, Tim Bradshaw  <···@cley.com> wrote:
>* Joel Ray Holveck wrote:
>
>> And if not, how do you intend to take advantage of their extended and
>> numbered capabilities?
>
>I think that you are right about his example, but there are other
>cases where, for instance there is a bird protocol for which the
>abstract class only defines part of the implementation, and for which
>various mixins define the rest.  All the user needs to know is that
>they have a class that implements the whole protocol, but there may be
>several ways of getting such a class.

But it's still the case that some component of the program has to make a
decision of *which* implementation to use.  Typically there's a MAKE-BIRD
function that takes a variety of parameters, and decides which subclass
and/or mixins to use based on those parameters.  You could do this in the
MAKE-INSTANCE method for BIRD -- as long as it returns some subclass of
BIRD it is satisfying its contract.

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3vg6gcd05.fsf@cley.com>
* Barry Margolin wrote:

> But it's still the case that some component of the program has to make a
> decision of *which* implementation to use.  Typically there's a MAKE-BIRD
> function that takes a variety of parameters, and decides which subclass
> and/or mixins to use based on those parameters.  You could do this in the
> MAKE-INSTANCE method for BIRD -- as long as it returns some subclass of
> BIRD it is satisfying its contract.

Yes, I agree almost completely, although I personally dislike
MAKE-INSTANCE returning anything but the exact class it was asked for,
so I'd use the MAKE-BIRD function for this.

There are, I think, cases where this knowledge of which class to use
is not localised all - my example earlier on where each class that is
interested in being a mixin in the final class registers itself and
then at some point you call a macro which defines the final class
based on all these mixins is such.  Of course, the answer here is `you
use the class you defined at the end' but if that class is just a
bunch of mixins, the information about how it works is completely
distributed.  But this scheme has all sorts of limitations.

--tim
From: Barry Margolin
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <JoS59.13$h1.3484@paloalto-snr1.gtei.net>
In article <···············@cley.com>, Tim Bradshaw  <···@cley.com> wrote:
>There are, I think, cases where this knowledge of which class to use
>is not localised all - my example earlier on where each class that is
>interested in being a mixin in the final class registers itself and
>then at some point you call a macro which defines the final class
>based on all these mixins is such.

I'd do it with a function, not a macro, since macros can't easily take
advantage of dynamic changes at runtime.  There's still a single function
that localizes the decision, but it bases it on the table that is
constructed by all those mixin registrations.

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3hei07wy9.fsf@cley.com>
* Barry Margolin wrote:
> I'd do it with a function, not a macro, since macros can't easily
> take advantage of dynamic changes at runtime.  There's still a
> single function that localizes the decision, but it bases it on the
> table that is constructed by all those mixin registrations.

Yes.  I think that this is hard without using some MOP stuff though,
isn't it?  You need to use ENSURE-CLASS I think (but I may be
confused...), while a macro can just expand into a DEFCLASS form.  Of
course ENSURE-CLASS is incredibly mild use of the MOP, so really I'm
stressing unduly.

--tim
From: Robert Strandh
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <85u1m1abcj.fsf@eta.emi.u-bordeaux.fr>
Joel Ray Holveck <·····@juniper.net> writes:

> > Sure, but then I would need a place in the program that knows about
> > the existence of both ebirds and nbirds, which I would like to avoid.
> > But that might be a solution if nothing else works.
> 
> Would whatever instantiates these birds know about them both?

No.  The `birds' module would typically instantiate what it thinks are
ordinary `birds'.  It has no knowledge of the other modules. 

> And if not, how do you intend to take advantage of their extended and
> numbered capabilities?

The `extended' module would define :after methods on `ebirds', and the
`numbered' module would define :after methods on `nbirds'.  Both the
`extended'and `numbered' modules are `spying' on birds by adding slots
to instances and defining :after methods.  They (`extended' and
`numbered') just don't know about each other.

Thanks to everyone for giving me more insight about this problem.  
-- 
Robert Strandh
From: Alain Picard
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <86hei01mgi.fsf@gondolin.local.net>
Robert Strandh <·······@labri.u-bordeaux.fr> writes:

> Joel Ray Holveck <·····@juniper.net> writes:
> 
> > > Sure, but then I would need a place in the program that knows about
> > > the existence of both ebirds and nbirds, which I would like to avoid.
> > > But that might be a solution if nothing else works.
> > 
> > Would whatever instantiates these birds know about them both?
> 
> No.  The `birds' module would typically instantiate what it thinks are
> ordinary `birds'.  It has no knowledge of the other modules. 

I think this is normally done using abstract factories.

I.e. you have some object, called the *bird-factory*,
which has a method CREATE-BIRD on it.  The part of your
application which _does_ know about all types of birds
creates a factory object which responds to CREATE-BIRD
by doing (make-instance 'extended-numbered-bird), or whatever
it is.

The rest of your code then does (CREATE-BIRD *bird-factory*)
and gets back something more specialized then BIRD.

It's a bit C++-ish, but is that the sort of thing you
were looking after?

I don't think you can escape having _someone_ somewhere
in your application knowing about the real type you want
to instantiate (but you can protect many modules against
that knowledge with the above trick).
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3bs88pgcd.fsf@cley.com>
* Alain Picard wrote:
> I.e. you have some object, called the *bird-factory*,
> which has a method CREATE-BIRD on it.  

*BIRD-FACTORY* needs to be no more than the name of the bird class (or
even the class itself (well, call it *BIRD-CLASS*)), and CREATE-BIRD
can be MAKE-INSTANCE.

> I don't think you can escape having _someone_ somewhere
> in your application knowing about the real type you want
> to instantiate (but you can protect many modules against
> that knowledge with the above trick).

Yes: something needs to set *BIRD-CLASS*.

--tim
From: Alain Picard
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <8665yg1fq7.fsf@gondolin.local.net>
Tim Bradshaw <···@cley.com> writes:

> *BIRD-FACTORY* needs to be no more than the name of the bird class (or
> even the class itself (well, call it *BIRD-CLASS*)), and CREATE-BIRD
> can be MAKE-INSTANCE.

i.e (make-instace *BIRD-CLASS*) ?

Hum.  This is what I get for having programmed C++ for too
many years.  :-)
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3hei0e218.fsf@cley.com>
* Alain Picard wrote:

> i.e (make-instace *BIRD-CLASS*) ?

yes.  Classes are factories, pretty much, in languages with
first-class classes.

Actually, I often make a wrapper like (make-bird) which calls
(MAKE-INSTANCE *BIRD-CLASS*), because I can get better control over
the signature of MAKE-BIRD than I can MAKE-INSTANCE and thus,
typically, better support in the environment for telling me what the
args are.  I'm also freed from having to worry about what birds
actually are - for instance there might be a horrid-but-fast version
which implements them as conses or something.

--tim
From: Dave Bakhash
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <c29bs7z4yba.fsf@nerd-xing.mit.edu>
Tim Bradshaw <···@cley.com> writes:

> Actually, I often make a wrapper like (make-bird) which calls
> (MAKE-INSTANCE *BIRD-CLASS*), because I can get better control over
> the signature of MAKE-BIRD than I can MAKE-INSTANCE and thus,
> typically, better support in the environment for telling me what the
> args are.

I've toiled around with this for a long time myself, and in the end have
come to the conclusion that if you're gonna use classes, then it's best
_not_ to get in the way of MAKE-INSTANCE.  I don't know how well of a
job I can do to justify this, but in general, I feel that key/value
pairs (or named parameters) are about the most general way to call a
function.  For me, probably the most important reason _not_ to define
alternative constructors is for consistency.  In a sense, what you put
in your constructor really should go in one of the predefined places
that was expressly designed for customizing initialization (typically
INITIALIZE-INSTANCE methods).  Standard method combination added with
object instantiation and initialization protocol is more than enough to
make objects just how you want.  In a sense, if you roll your own
constructor, then (to me) you're in a sense breaking the protocol
(in the sense of convention and style).  I realize that this is a very
personal preference, but I feel strongly enough about it to stand behind
it.  I've seen, for example in Keene's book on CLOS, that it is even
suggested that programmers write their own constructors which in turn
call MAKE-INSTANCE.  I disagree.

Just to offer a simple example of the flexibility and generality of
the gf MAKE-INSTANCE, take this:

> I'm also freed from having to worry about what birds actually are -
> for instance there might be a horrid-but-fast version which implements
> them as conses or something.

one could do:

(defmethod make-instance ((class (eql 'bird)) &rest initargs)
  (cons 'person initargs))

My point being that there's enough flexibility in the machinery of
MAKE-INSTANCE to _not_ want to write your own `make-foo' functions.

There are other reasons as well.  I'd like to see cases where people
feel strongly that writing more specialized constructors is warranted.

dave
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey34rdrg48w.fsf@cley.com>
* Dave Bakhash wrote:
> I don't know
> how well of a job I can do to justify this, but in general, I feel
> that key/value pairs (or named parameters) are about the most
> general way to call a function.  For me, probably the most important
> reason _not_ to define alternative constructors is for consistency.
> In a sense, what you put in your constructor really should go in one
> of the predefined places that was expressly designed for customizing
> initialization (typically INITIALIZE-INSTANCE methods).  Standard
> method combination added with object instantiation and
> initialization protocol is more than enough to make objects just how
> you want.

Yes.  I don't think I would ever do significant (or any)
initialisation work in a defined constructor function.  CLOS is enough
for that.  Likewise for non-trivial signatures I use keywords.
Typically my constructors are inlined functions which simply call
MAKE-INSTANCE and return the result, with possibly some translation
and validation of keyword names &c.

The two reasons for specific constructors that I want good
documentation from the environment, and asking for the arglist of
MAKE-INSTANCE is typically not informative.

The second reason is that I might not want to commit to using
instances at all, and ...

> (defmethod make-instance ((class (eql 'bird)) &rest initargs)
>   (cons 'person initargs))

... I would never, ever do this.  This code, as it stands, is going to
break in exciting ways, because

    (make-instance (find-class 'bird))

will break and should not.  The least way you can make it work is to
have something like:

    (defclass bird () ())

(note: DEFTYPE is not enough), and then define

   (defmethod make-instance ((class 
                              (eql #.(find-class 'bird))) ...)
     ...)

But I'm not sure that that will work even (can you compile a file with
this in?).

If that can be made to work (which I'm sure it can), then I still
would not ever do this, because I want:

    (defun find-class* (c)
      (ecase c
        (class c)
        (symbol (find-class c))))

    (eql (class-of (make-instance c ...)) (find-class* c))

To always be true (when it's not an error).  I don't think that the
standard requires this (in fact, I'm fairly sure that it doesn't), but
I would never write programs where it was not true, so if my
representation might not be an instance, then I need a constructor.

--tim
From: Dave Bakhash
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <c297kin4mss.fsf@nerd-xing.mit.edu>
Tim,

I agree with some of your points...and to argue it out would be
difficult because I think it's more stylistic than anything else.  But
here are some comments.

Tim Bradshaw <···@cley.com> writes:

> Typically my constructors are inlined functions which simply call
> MAKE-INSTANCE and return the result, with possibly some translation
> and validation of keyword names &c.

Yes.  If you are going to intercept MAKE-INSTANCE, then this is as far
as I would go as well.  It would be like defining a :constructor lambda
list with DEFSTRUCT (where you can play around with the arguments of the
MAKE-<struct-name> functions).

> ...asking for the arglist of MAKE-INSTANCE is typically not
> informative.

Yes.  True...but you have all of the metadata at hand, so if you wanted,
you could probably write your own function like DOCUMENTATION* that
examined the direct and effective slots of a class, determined which had
initargs and initforms, etc.  Though I'm not 100% sure this could be
done just right, it seems possible to do this generically, in a
_derived_ way.  It might look like:

 (documentation* #'make-instance 'bird)

where the non-optional parameters of the gf are provided.  I'm not
terribly sure, but it's a start.

> The second reason is that I might not want to commit to using
> instances at all, and ...
> 
> > (defmethod make-instance ((class (eql 'bird)) &rest initargs)
> >   (cons 'person initargs))
> 
> ... I would never, ever do this.  This code, as it stands, is going to
> break in exciting ways, because

I wasn't suggesting doing that.  However, when you break CLOS (in a
sense, by not using CLOS objects), you are already asking for trouble.
Consider that:

 1) a method for:

>     (make-instance (find-class 'bird))

could also be defined, and therefore would not break

 2) 

> The least way you can make it work is to have something like:

>     (defclass bird () ())
> 
>    (defmethod make-instance ((class 
>                               (eql #.(find-class 'bird))) ...)
>      ...)
> 
> But I'm not sure that that will work even (can you compile a file with
> this in?).

If you're trying to use an alternative storage mechanism (i.e. not using
STANDARD-OBJECT) and yet somehow maintain the other benefits of CLOS,
then you're asking for a lot, and that's not part of the contract.  So
you're necessarily in your own boat.  Just like I'm not against the
MAKE-FOO constructors for structs, I'm against them in general; I'm just
against them for tried and true CLOS objects.  If it's not a CLOS
object, then I'm not arguing for or against it having a MAKE-FOO
constructor.

> If that can be made to work (which I'm sure it can), then I still
> would not ever do this, because I want:
> 
>     (defun find-class* (c)
>       (ecase c
>         (class c)
>         (symbol (find-class c))))
> 
>     (eql (class-of (make-instance c ...)) (find-class* c))
> 
> To always be true (when it's not an error).  I don't think that the
> standard requires this (in fact, I'm fairly sure that it doesn't), but
> I would never write programs where it was not true, so if my
> representation might not be an instance, then I need a constructor.

I would certainly try to make sure that the above constraint is met as
well.  I don't think it is relevant to what I'm arguing, which is simply
that constructors for CLOS objects other than MAKE-INSTANCE be avoided.

dave
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3wuqn41r3.fsf@cley.com>
* Dave Bakhash wrote:

> If you're trying to use an alternative storage mechanism (i.e. not
> using STANDARD-OBJECT) and yet somehow maintain the other benefits
> of CLOS, then you're asking for a lot, and that's not part of the
> contract.  So you're necessarily in your own boat.  Just like I'm
> not against the MAKE-FOO constructors for structs, I'm against them
> in general; I'm just against them for tried and true CLOS objects.
> If it's not a CLOS object, then I'm not arguing for or against it
> having a MAKE-FOO constructor.

Well, a major place where I use them is where I want to remain (or I
want the user to remain) agnostic about whether the objects are
STANDARD-OBJECTs or not, and where I might want to change the
representation.

As an example I have a structure (not in any technical sense) called a
`generic tree' (which is a bad name in very many ways), which is
really just your standard tree-with-values-and-labelled-arcs.  They
were originally invented to do switch parsing from a command-line - I
wanted to have long options but be able to type minimum unique
prefixes, and the obvious approach is to intern all the options in a
tree.  So there are a whole load of creation and access functions.

But it's all just a hack on top of conses at present:

(pprint (let ((gt (make-gt)))
          (intern-sequence-in-gt "--boot-file" gt :value 1)
          (intern-sequence-in-gt "--boot-verbose" gt :value 2)
          gt))

(((#\-
   ((#\-
     ((#\b
       ((#\o
         ((#\o
           ((#\t
             ((#\-
               ((#\v ((#\e ((#\r ((#\b ((#\o ((#\s ((#\e nil . 2)))))))))))))
                (#\f ((#\i ((#\l ((#\e nil . 1)))))))))))))))))))))))

This is actually OK for the purposes it serves, but I definitely don't
want users of the code either to assume that GTs are just conses, or
that they are any other kind of object in particular.  So I want the
abstraction of creation (and access) functions, but I can't use
MAKE-INSTANCE.  This is not the only place where I have code that is
like this, either.

--tim
From: Dave Bakhash
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <c298z33q79j.fsf@no-knife.mit.edu>
Tim Bradshaw <···@cley.com> writes:

> > If you're trying to use an alternative storage mechanism (i.e. not
> > using STANDARD-OBJECT) and yet somehow maintain the other benefits
> > of CLOS, then you're asking for a lot, and that's not part of the
> > contract.  So you're necessarily in your own boat.  Just like I'm
> > not against the MAKE-FOO constructors for structs, I'm against them
> > in general; I'm just against them for tried and true CLOS objects.
> > If it's not a CLOS object, then I'm not arguing for or against it
> > having a MAKE-FOO constructor.
> 
> Well, a major place where I use them is where I want to remain (or I
> want the user to remain) agnostic about whether the objects are
> STANDARD-OBJECTs or not, and where I might want to change the
> representation.

fair enough.  Funny, though...there have been times where I thought that
implementations should define a constructor for MAKE-INSTANCE that would
simply call the (default) `make-foo' constructor for structure-class
objects, e.g.

(defstruct bird
  foo
  bar)

(make-instance 'bird :foo fum :bar baz)

==> (make-bird :foo fum :bar baz)

In fact, it's not too hard to do this by defining a defstruct-like macro
that adds the extra methods to MAKE-INSTANCE, etc.

The reason it would be nice (at times such as the one you mentioned) is
that DEFSTRUCT can use conses or vectors inside, named or not named.  So
where there is overlapping with DEFCLASS (e.g. the need for a
constructor, hence MAKE-INSTANCE) it's sometimes nice to stick to one
way (i.e. the more general one: MAKE-INSTANCE).

I'm guessing that in cases where the :constructor option is used in
DEFSTRUCT using a generalized MAKE-INSTANCE may be problematic.  But
it's a thought.

Just a final note...

In Java, specifically, there is only one kind of class (as opposed to
classes and structs).  So, AFAIK, they create new instances of their
classes with the generalized "new" operator.  In a sense, I'm just
noting how it might be possible to do the same in CL for structs and
classes.

dave
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3ofbyvrt5.fsf@cley.com>
* Dave Bakhash wrote:

> The reason it would be nice (at times such as the one you mentioned)
> is that DEFSTRUCT can use conses or vectors inside, named or not
> named.  So where there is overlapping with DEFCLASS (e.g. the need
> for a constructor, hence MAKE-INSTANCE) it's sometimes nice to stick
> to one way (i.e. the more general one: MAKE-INSTANCE).

Ah but what it can't do is define really mutant things.  My GT
objects, for instance, are (currently) a cons of

    (children . value)

where children is an alist of (key . node).  You can't define things
like this with DEFSTRUCT.  Of course you might argue that I shouldn't
be stressing about saving a cons cell, but, well, I did.

(Interestingly, InterLisp had some fairly elaborate mechanism for
letting you define `structures' (I think it called them `records') with
pretty much arbitrary layouts as conses.  At some point I cloned this
for Elisp, I think, but it's long lost now, and I don't have the IRM
any more to even remember how it worked.)

--tim
From: Lieven Marchand
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <87r8gu21de.fsf@wyrd.be>
Tim Bradshaw <···@cley.com> writes:

> (Interestingly, InterLisp had some fairly elaborate mechanism for
> letting you define `structures' (I think it called them `records') with
> pretty much arbitrary layouts as conses.  At some point I cloned this
> for Elisp, I think, but it's long lost now, and I don't have the IRM
> any more to even remember how it worked.)

I had a cursory glance through the one at
http://www.spies.com/~aek/pdf/xerox/interlisp/ but I couldn't find
what you referred to.

-- 
Bored, now.
Lieven Marchand <···@wyrd.be>
From: Rahul Jain
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <873ctawvas.fsf@photino.localnet>
Tim Bradshaw <···@cley.com> writes:

> As an example I have a structure (not in any technical sense) called a
> `generic tree' (which is a bad name in very many ways), which is
> really just your standard tree-with-values-and-labelled-arcs. [...] I
> wanted to have long options but be able to type minimum unique
> prefixes, and the obvious approach is to intern all the options in a
> tree.

> (((#\-
>    ((#\-
>      ((#\b
>        ((#\o
>          ((#\o
>            ((#\t
>              ((#\-
>                ((#\v ((#\e ((#\r ((#\b ((#\o ((#\s ((#\e nil . 2)))))))))))))
>                 (#\f ((#\i ((#\l ((#\e nil . 1)))))))))))))))))))))))

This data structure is commonly called a 'trie' or a 'discrimination
net'. Here you seem to be using alists as the nodes. Other options are
vectors and hashtables (with the latter, they can be called
'hash-tries'). PAIP does an implementation of tries for matching
complete expressions in order to implement its prolog pattern-matching
engine.

-- 
-> -/                        - Rahul Jain -                        \- <-
-> -\  http://linux.rice.edu/~rahul -=-  ············@techie.com   /- <-
-> -X "Structure is nothing if it is all you got. Skeletons spook  X- <-
-> -/  people if [they] try to walk around on their own. I really  \- <-
-> -\  wonder why XML does not." -- Erik Naggum, comp.lang.lisp    /- <-
|--|--------|--------------|----|-------------|------|---------|-----|-|
   (c)1996-2002, All rights reserved. Disclaimer available upon request.
From: Rahul Jain
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <877kimwvk0.fsf@photino.localnet>
Dave Bakhash <·····@alum.mit.edu> writes:

> Yes.  True...but you have all of the metadata at hand, so if you wanted,
> you could probably write your own function like DOCUMENTATION* that
> examined the direct and effective slots of a class, determined which had
> initargs and initforms, etc.  Though I'm not 100% sure this could be
> done just right, it seems possible to do this generically, in a
> _derived_ way.  It might look like:
> 
>  (documentation* #'make-instance 'bird)
> 
> where the non-optional parameters of the gf are provided.  I'm not
> terribly sure, but it's a start.

My previous followup in this thread mentioned defdoc.umentation. Here
is the syntax for this operation that I am currently using, just for
anyone who is curious:

(initargs (defdoc.umentation 'bird 'type #| or 'class |#))

-- 
-> -/                        - Rahul Jain -                        \- <-
-> -\  http://linux.rice.edu/~rahul -=-  ············@techie.com   /- <-
-> -X "Structure is nothing if it is all you got. Skeletons spook  X- <-
-> -/  people if [they] try to walk around on their own. I really  \- <-
-> -\  wonder why XML does not." -- Erik Naggum, comp.lang.lisp    /- <-
|--|--------|--------------|----|-------------|------|---------|-----|-|
   (c)1996-2002, All rights reserved. Disclaimer available upon request.
From: Rahul Jain
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <87fzxaww20.fsf@photino.localnet>
Tim Bradshaw <···@cley.com> writes:

[Instead of specializing on the symbol, ]
>    (defmethod make-instance ((class 
>                               (eql #.(find-class 'bird))) ...)
>      ...)
> 
> But I'm not sure that that will work even (can you compile a file with
> this in?).

Yes. I have done it even without the #. on both cmucl and
lispworks. Lispworks raises a continuable error that I'm "redefining"
a function in the CL package, which I consider a bug, since this
"redefinition" uses an identifier from my package (technically,
indirectly from it).


> If that can be made to work (which I'm sure it can), then I still
> would not ever do this, because I want:
> 
>     (defun find-class* (c)
>       (ecase c
>         (class c)
>         (symbol (find-class c))))
> 
>     (eql (class-of (make-instance c ...)) (find-class* c))
> 
> To always be true (when it's not an error).  I don't think that the
> standard requires this (in fact, I'm fairly sure that it doesn't), but
> I would never write programs where it was not true, so if my
> representation might not be an instance, then I need a constructor.

That's a reasonable point. The case where I used it actually was only
to provide a restart to promote the class to its superclass if some
more generic options were chosen (from discretionary-hyphen to a
general discretionary-break; the discretionary-hyphen had some slots
with class allocation and error-raising setf-methods).

-- 
-> -/                        - Rahul Jain -                        \- <-
-> -\  http://linux.rice.edu/~rahul -=-  ············@techie.com   /- <-
-> -X "Structure is nothing if it is all you got. Skeletons spook  X- <-
-> -/  people if [they] try to walk around on their own. I really  \- <-
-> -\  wonder why XML does not." -- Erik Naggum, comp.lang.lisp    /- <-
|--|--------|--------------|----|-------------|------|---------|-----|-|
   (c)1996-2002, All rights reserved. Disclaimer available upon request.
From: Pekka P. Pirinen
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <uvg64rtd7.fsf@globalgraphics.com>
Rahul Jain <·····@rice.edu> writes:
> Tim Bradshaw <···@cley.com> writes:
> >    (defmethod make-instance ((class 
> >                               (eql #.(find-class 'bird))) ...)
> >      ...)
> > 
> > But I'm not sure that that will work even (can you compile a file with
> > this in?).
> 
> Yes. I have done it even without the #. on both cmucl and
> lispworks.

With #. it's not portable, because a class is not necessarily a
externalizable.

> Lispworks raises a continuable error that I'm "redefining"
> a function in the CL package, which I consider a bug, since this
> "redefinition" uses an identifier from my package (technically,
> indirectly from it).

Protecting the CL package is a technical feature.  LW implements the
standard rule from point 19 of ANS 11.1.2.1.2 (which is mostly about
redefinition, hence the message; "modifying" might be a better term).

There might be ways to improve the rule, but I cannot think of one
ATM.  It's not good enough to say the instance was computed using a
non-protected identifier, since standard things can be designated in
non-standard ways, but they should still be protected.

The standard way around this, as you probably know, is to define a
metaclass for BIRD, since the problem is that the class is a direct
instance of STANDARD-CLASS.
-- 
Pekka P. Pirinen
Don't pay for junk email.  It's your private mailbox, and they're abusing
it.  If you don't complain, they'll do it again.  Don't hit delete, get
*them* deleted.  See <http://www.uk.spam.abuse.net/spam/> for info.
From: Rahul Jain
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <87bs7ywvss.fsf@photino.localnet>
(oops, forgot to reply to this point in the previous message.)

Tim Bradshaw <···@cley.com> writes:

> The two reasons for specific constructors that I want good
> documentation from the environment, and asking for the arglist of
> MAKE-INSTANCE is typically not informative.

You can enumerate the initargs of a specific class using the
MOP. DefDoc.umentation (if you recall my document layout program from
before, this is a module of that for docstrings) will have an INITARGS
keyword for classes where you can describe the initargs
added/overridden by that class. The idea will be to have this
accessible via something similar to hyperspec-lookup.

Hopefully this will make seeing how to deal with the initialization of
classes much simpler.

If you'd like to see the prototype syntax for DefDoc(.umentation), visit
http://linux.rice.edu/~rahul/DefDoc/
Any and all feedback is appreciated.

-- 
-> -/                        - Rahul Jain -                        \- <-
-> -\  http://linux.rice.edu/~rahul -=-  ············@techie.com   /- <-
-> -X "Structure is nothing if it is all you got. Skeletons spook  X- <-
-> -/  people if [they] try to walk around on their own. I really  \- <-
-> -\  wonder why XML does not." -- Erik Naggum, comp.lang.lisp    /- <-
|--|--------|--------------|----|-------------|------|---------|-----|-|
   (c)1996-2002, All rights reserved. Disclaimer available upon request.
From: Thomas F. Burdick
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <xcvsn1ag1d1.fsf@hurricane.OCF.Berkeley.EDU>
Dave Bakhash <·····@alum.mit.edu> writes:

> My point being that there's enough flexibility in the machinery of
> MAKE-INSTANCE to _not_ want to write your own `make-foo' functions.
> 
> There are other reasons as well.  I'd like to see cases where people
> feel strongly that writing more specialized constructors is warranted.

Well, that depends ... do you consider factories to be constructors?
Once in a very great while (and usually after I've been using
Smalltalk), I find a situation that's best expressed using a factory.
It's not really a constructor, because although it's called MAKE-FOO,
usually

  (eql (class-of (make-foo ...)) (find-class 'foo)) => nil

and class FOO isn't really meant to be instantiated, hence the
factory.  The actual class to be created is determined by the
arguments to the GF MAKE-FOO, so the user doesn't need to worry about
exactly what class it is that implements the functionality s/he was
requesting.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Kaz Kylheku
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <aja21q$j37$2@luna.vcn.bc.ca>
In article <··············@gondolin.local.net>, Alain Picard wrote:
> Robert Strandh <·······@labri.u-bordeaux.fr> writes:
> 
>> Joel Ray Holveck <·····@juniper.net> writes:
>> 
>> > > Sure, but then I would need a place in the program that knows about
>> > > the existence of both ebirds and nbirds, which I would like to avoid.
>> > > But that might be a solution if nothing else works.
>> > 
>> > Would whatever instantiates these birds know about them both?
>> 
>> No.  The `birds' module would typically instantiate what it thinks are
>> ordinary `birds'.  It has no knowledge of the other modules. 
> 
> I think this is normally done using abstract factories.

Abstract factories are a ``design pattern'' for making a group of objects from
different classes that are related together.

For example, suppose you have a class hierarchy in which the two basic
abstractions are encryption keys (objects holding security tokens) and
encryptors (object which actually perform cryptographic operations).  Now it
comes to reason that a given encryption key goes with a matching encryptor; a
DES encryption key goes with a DES encryptor and so forth.  So you invent an
encryption factory which makes corresponding keys and encryptors, a DES factory
then makes DES encryptors and keys, an IDEA factory makes IDEA keys and
encryptors and so forth.

I'm not so sure that abstract factories are that useful in Lisp;
maybe someone else can shed a light here.

> I.e. you have some object, called the *bird-factory*,
> which has a method CREATE-BIRD on it.  The part of your
> application which _does_ know about all types of birds
> creates a factory object which responds to CREATE-BIRD
> by doing (make-instance 'extended-numbered-bird), or whatever
> it is.

If you just have one class, then the factory is somewhat of a useless
indirection. Because you will still have to know to use the right factory; to
get an extended-bird, you have to go to the extended-bird factory. You do get
the advantage that you can pass the factory to places which don't know its
exact type, and those places can still manufacture the birds that you intended.
In Lisp a simple function-object could serve as such a factory.

  
  (let ((extended-bird-factory #'(lambda (args) 
                                   (apply make-instance 
                                          'extended-numbered-bird args))))
     ...)


> The rest of your code then does (CREATE-BIRD *bird-factory*)
> and gets back something more specialized then BIRD.
> 
> It's a bit C++-ish, but is that the sort of thing you
> were looking after?

It is a bit C++ ish. The reason you need factories is not just for the family
of related objects, but also to serve as a translation framework which converts
some parameters into constructor calls. For example, a Unix command-line
program written in C++ might have a factory in it somewhere which constructs
objects based on parsing an argument vector from the main() function.  In Lisp
it's a whole lot easier to manufacture data structures that correspond to some
input format.
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3adntsmj0.fsf@cley.com>
* Robert Strandh wrote:

> Sure, but then I would need a place in the program that knows about
> the existence of both ebirds and nbirds, which I would like to avoid.
> But that might be a solution if nothing else works. 

You can probably avoid this if you are willing to do a little mucking
around.  One thing would be to have a list of `mixins you should use
when making a bird class' with which you register various classes.
Then you have a DEFINE-BIRD-CLASS macro which looks something like
this

(defmacro define-bird-class (name (&rest other-mixins) &body class-forms)
  `(defclass ,name (bird ,@other-mixins ,@*bird-mixins*) ,@other-forms))

All this depends on *BIRD-MIXINS* being correct at macro expansion
time, so you need to think about load order and/or EVAL-WHEN and so
on.

If you are willing to rely on (the more innocuous bits of) the/a MOP
then things can be made even simpler I think (or more general - no
need for special macros per class you want to mix.

Note that one reason not to do the horrid CHANGE-CLASS trick is that
it's often *really* expensive.  CHANGE-CLASS is not something that
typically gets optimized in my experience since it's not used a whole
lot I assume.

(Another reason, I think, is that you ought to be able to assume that
(MAKE-INSTANCE 'FOO) returns a FOO, not some class related to FOO).

--tim
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3ofc9qxv4.fsf@cley.com>
* I wrote:

> You can probably avoid this if you are willing to do a little mucking
> around.  One thing would be to have a list of `mixins you should use
> when making a bird class' with which you register various classes.
> Then you have a DEFINE-BIRD-CLASS macro which looks something like
> this

In fact, something like this might work.

--tim

--cut--
;;;;
;;;

(in-package :cl-user)

(defvar *class-mixers* '())

(defmacro declare-mixin (name &body class-names)
  ;; Is this EVAL-WHEN right?
  `(eval-when (:compile-toplevel :load-toplevel :execute)
     (loop with found = (or (assoc ',name *class-mixers*)
                            (let ((new (cons ',name nil)))
                              (push new *class-mixers*)
                              new))
           for n in ',class-names
           do (setf (cdr found) (nconc (cdr found) (list n)))
           finally (return ',name))))

(defmacro define-mixed-class (name (&rest other-mixins) &body defclass-body)
  (let ((found (assoc name *class-mixers*)))
    (when (null found)
      (warn "No mixins have been declared for ~S" name))
    (let ((slotds (car defclass-body))
          (opts (cdr defclass-body)))
      `(defclass ,name (,@other-mixins ,@(cdr found))
         ,slotds ,@opts))))

#||

(defclass abstract-bird ()
  ())

(declare-mixin bird
  abstract-bird)

(defclass barian ()
  ())

(declare-mixin bird
  barian)

(define-mixed-class bird ())
||#

  
From: Tim Bradshaw
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <ey3k7mxqvlt.fsf@cley.com>
* I wrote:
> In fact, something like this might work.

Or even this, sorry for previous buggy version.

--tim

--cut--
;;;;
;;;

(in-package :cl-user)

(defvar *class-mixers* '())

(defmacro declare-mixin (name &body class-names)
  ;; Is this EVAL-WHEN right?
  `(eval-when (:compile-toplevel :load-toplevel :execute)
     (loop with found = (or (assoc ',name *class-mixers*)
                            (let ((new (cons ',name nil)))
                              (push new *class-mixers*)
                              new))
           for n in ',class-names
           unless (member n (cdr found))
           do (setf (cdr found) (nconc (cdr found) (list n)))
           finally (return ',name))))

(defmacro define-mixed-class (name (&rest other-mixins) &body defclass-body)
  (let ((found (assoc name *class-mixers*)))
    (when (null found)
      (warn "No mixins have been declared for ~S" name))
    (let ((slotds (car defclass-body))
          (opts (cdr defclass-body)))
      `(defclass ,name (,@other-mixins ,@(cdr found))
         ,slotds ,@opts))))

#||

(defclass abstract-bird ()
  ())

(declare-mixin bird
  abstract-bird)

(defclass barian ()
  ())

(declare-mixin bird
  barian)

(define-mixed-class bird ())
||#

  
From: Kaz Kylheku
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <aj68ro$c2u$2@luna.vcn.bc.ca>
In article <··············@eta.emi.u-bordeaux.fr>, Robert Strandh wrote:
> When (say) a bird is created by (make-instance 'bird) in the `bird'
> module, I really want for an extended and numbered bird to be
> created. 

Maybe this is the crux of your problem; you want unreasonable things to happen.
You want (make-instance 'bird) to create something other than just a bird, but
you don't want the bird class having knowledge about these extensions.
How about:

  (defun make-preferred-bird ()
    (make-instance 'nbird)) ;; change later to ebird, as needed

The function knows about the various bird modules, which spares them from
knowing about each other.  If you want the properties of two derived birds in
one class, such as numbered-bird and extended-bird, and you don't want the
clases to derive from each other, or even know about each other, then use
multiple inheritance.

  (defclass extended-bird (bird) (...))
  (defclass numbered-bird (bird) (...))
  (defclass extended-numbered-bird (extended-bird numbered-bird) (...))
  (defun make-preferred-bird () (make-instance 'extended-numbered-bird))
From: Thomas F. Burdick
Subject: Re: Q: modularity problem with CLOS
Date: 
Message-ID: <xcvsn1lz2ux.fsf@conquest.OCF.Berkeley.EDU>
Kaz Kylheku <···@ashi.footprints.net> writes:

> In article <··············@eta.emi.u-bordeaux.fr>, Robert Strandh wrote:
> > When (say) a bird is created by (make-instance 'bird) in the `bird'
> > module, I really want for an extended and numbered bird to be
> > created. 
> 
> Maybe this is the crux of your problem; you want unreasonable things to happen.
> You want (make-instance 'bird) to create something other than just a bird, but
> you don't want the bird class having knowledge about these extensions.

Well, his trivial example code makes the problem seem unreasonable,
but there are reasonable times to want to do something like this.  Say
EBIRD and NBIRD aren't about birds at all, but are modules for
debugging, and visualization, respectively.  It's nice to preserve the
conceptual orthogonality or production code, debugging code, and
visualization code, with orthogonal modules.  I think that Lisp is one
of only a few languages where preserving this orthogonality isn't an
unreasonable thing to do.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'