Home > Archive > Smalltalk > April 2004 > Multiple inheritance in Smalltalk: is it really needed?
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
Multiple inheritance in Smalltalk: is it really needed?
|
|
| Vincent Foley 2004-03-27, 12:27 am |
| Hi everyone,
I have some friends (some really anal friends one might say) who are
pretty coders and refuse to even consider trying Smalltalk. Their reason?
An object oriented that does not support multiple inheritance is not even
worth talking about. I'm quite new to Smalltalk, so I haven't done
anything very big so far that would've maybe required MI.
But I want to ask, what is your opinion on MI? I heard Ron Jeffries say
that it's not even needed, so how do you work around it?
Thanks for your opinions,
Vince.
--
Vincent Foley-Bourgon
Email: vinfoley@iquebec.com
Blog: http://www.livejournal.com/~gnuvince/
| |
| Chris Lopeman 2004-03-27, 12:27 am |
| Search the archives, this has been discussed to death. Also search for
things like traits and Mix-ins. However, to get your friends to try it
just tell them that Smalltalk gives them a dozen other advantages that
thier current language does not. So it's worth checking out.
Vincent Foley wrote:
> Hi everyone,
>
> I have some friends (some really anal friends one might say) who are
> pretty coders and refuse to even consider trying Smalltalk. Their reason?
> An object oriented that does not support multiple inheritance is not even
> worth talking about. I'm quite new to Smalltalk, so I haven't done
> anything very big so far that would've maybe required MI.
>
> But I want to ask, what is your opinion on MI? I heard Ron Jeffries say
> that it's not even needed, so how do you work around it?
>
> Thanks for your opinions,
> Vince.
>
| |
| Darin Johnson 2004-03-27, 12:27 am |
| Vincent Foley <vinfoley@iquebec.com> writes:
> I have some friends (some really anal friends one might say) who are
> pretty coders and refuse to even consider trying Smalltalk. Their reason?
> An object oriented that does not support multiple inheritance is not even
> worth talking about.
I'm guessing from the "not worth talking about" description that you
really meant to say "petty coders".
Depending upon what language they're using, you can respond with: "any
language that doesn't let you modify the compiler on the fly isn't
worth talking about", or "any language that doesn't have first class
blocks isn't worth talking about".
It all comes down to what prejudices exist. Multiple inheritance can
indeed be useful, but it's certainly not very high up on the "must
have" list of OO features. The lack of MI is easy to work around in
Smalltalk, because it isn't statically typed.
--
Darin Johnson
"Floyd here now!"
| |
| James A. Robertson 2004-03-27, 12:27 am |
| On Wed, 17 Mar 2004 18:18:19 -0500, Vincent Foley
<vinfoley@iquebec.com> wrote:
>Hi everyone,
>
>I have some friends (some really anal friends one might say) who are
>pretty coders and refuse to even consider trying Smalltalk. Their reason?
>An object oriented that does not support multiple inheritance is not even
>worth talking about. I'm quite new to Smalltalk, so I haven't done
>anything very big so far that would've maybe required MI.
>
In a dynamic language, where you have completely unlimited
polymorphism, this is far less of an issue. i.e, I can have two
objects that are unrelated from a hierarchical standpoint support the
same API (or part of the same API). This is akin to Java interfaces,
but on an informal, ad-hoc basis
I can't actually recall wanting MI in over 10 years of ST development.
There are cases (ReadWriteStream comes to mind) where it would make
things a trifle simpler; but those cases are rare in ST
>But I want to ask, what is your opinion on MI? I heard Ron Jeffries say
>that it's not even needed, so how do you work around it?
>
>Thanks for your opinions,
>Vince.
<Talk Small and Carry a Big Class Library>
James Robertson, Product Manager, Cincom Smalltalk
http://www.cincomsmalltalk.com/blog/blogView
| |
| Vincent Foley 2004-03-27, 12:27 am |
| On Thu, 18 Mar 2004 00:20:05 +0000, Darin Johnson wrote:
> I'm guessing from the "not worth talking about" description that you
> really meant to say "petty coders".
>
Actually, one of them is pretty hot a programmer, but he discriminates
against anything he doesn't use.
> Depending upon what language they're using, you can respond with: "any
> language that doesn't let you modify the compiler on the fly isn't
> worth talking about", or "any language that doesn't have first class
> blocks isn't worth talking about".
>
Both are using Python. Python is dynamic and flexible enough to allow
much of what Smalltalk allows (with more work in many cases). Smalltalk's
real advantage over Python in my opinion is its environment: Vim or Emacs
coupled with an interpreter is a far cry from all the powerful tools found
in a standard Smalltalk environment
> It all comes down to what prejudices exist. Multiple inheritance can
> indeed be useful, but it's certainly not very high up on the "must
> have" list of OO features. The lack of MI is easy to work around in
> Smalltalk, because it isn't statically typed.
I found an example in Squeak where it could be useful. You have
ReadStream class and a WriteStream class. To make up the ReadWriteStream
class, it would probably be the natural thing to do.
But what if I wanted to have a class with behaviors from two super
classes, what would I need to do? I have heard about "delegating", but I
am not sure what that means.
I also know Ruby, and I kind of like the mix-in solution:
module Dog
def bark()
puts "Bow wow"
end
end
module Animal
def walk()
puts "Clip clap clip clap"
end
end
module Bulldog
include Dog, Animal
end
Is there a way to do that in Smalltalk without having to use deep black
magic?
--
Vincent Foley-Bourgon
Email: vinfoley@iquebec.com
Blog: http://www.livejournal.com/~gnuvince/
| |
| Randy A. Ynchausti 2004-03-27, 12:27 am |
| Vincent,
> I have some friends (some really anal friends one might say) who are
> pretty coders and refuse to even consider trying Smalltalk. Their reason?
> An object oriented that does not support multiple inheritance is not even
> worth talking about.
Please ask them why C# in the .NET Environment does not have multiple
inheritance. I am curious to hear whether C# is or isn't an object-oriented
language.
Regards,
Randy
| |
| Ian Upright 2004-03-27, 12:27 am |
| Vincent Foley <vinfoley@iquebec.com> wrote:
>Hi everyone,
>
>I have some friends (some really anal friends one might say) who are
>pretty coders and refuse to even consider trying Smalltalk. Their reason?
>An object oriented that does not support multiple inheritance is not even
>worth talking about. I'm quite new to Smalltalk, so I haven't done
>anything very big so far that would've maybe required MI.
>
>But I want to ask, what is your opinion on MI? I heard Ron Jeffries say
>that it's not even needed, so how do you work around it?
>
>Thanks for your opinions,
>Vince.
I think MI as implemented in C++, etc.. is nearly pointless. However, I
really do believe Mixin's have great value. S#, a Smalltalk language
varient, has support for Mixins and Interfaces. I think Mixin's are the
future for all languages, and I really do believe they can achieve better
reuse and make it easier to simplify object designs that would be otherwise
excessively complicated. That said, Smalltalk is quite a fantastic language
without multiple inheritance, and there is a whole world of Java programmers
that seem to think we don't need multiple inheritance *or* proper
polymorphism. <g>
I think as we follow down the object-oriented path, our concentration will
be far more focused on behavior rather than structure. Behavior is
something that is entirely reusable, but structure is highly confining.
Objects (or attributes) can potentially have a multitude of ways of being
represented, be it in an instance variable, in some kind of hash table,
database monkier, proxy, and a multitude of other possibities.
If you have potentially an enormous amount of attributes on a particular
object, but only some of the time is an attribute actually used on a
particular set of objects, then huge memory-space savings can be made by
putting these attributes in some kind of hashtable lookup, instead of
overly-large objects with lots of empty instance variables. In other
objects, you might want to put these attributes directly in an instance
variable, gaining performance over space. Perhaps in these cases you might
have a handful of attributes represented in instance variables, and the rest
represented via a hashtable.
Now you want to share the implementation and behavior accross an assortment
of objects that may or may not have a different *structure*. With Mixin's,
this is trivial. With a class hierarchy where structure and behavior is
combined into something that is one in the same, this isn't so easy.
If one goes down this road far enough, you could envision a system whereby
*all* behavior belongs as a Mixin, and you just slap on some structure to
make it work the way you want.
I believe the future of object computing and databases is one where we
define the behaviors we need, and we leave it to the databases and the VM's
to help us optimize and rearrange how the data is structured. This lets us
choose things like space vs. speed, of which these decisions should be made
later rather than sooner. Make it work first, then optimize it after you
know exactly what you want and need. With traditional single-inheritance,
the structure of an object is often fixed in stone far too early in the
process.
Programmers need to be focused more on behavior and less on structure, and
we need to be able to refactor the stuctures easily without affecting the
underlying behavior or changing the behavioral heirarchy.
It's my opinion that Mixin's go a long way in aiding all this.
Ian
---
http://www.upright.net/ian/
| |
| L. M. Rappaport 2004-03-27, 12:27 am |
| On Thu, 18 Mar 2004 08:33:20 GMT, Ian Upright <ian-news@upright.net>
wrote (with possible editing):
....snip
>I think MI as implemented in C++, etc.. is nearly pointless.
Hah! So do I!
>However, I
>really do believe Mixin's have great value. S#, a Smalltalk language
>varient, has support for Mixins and Interfaces. I think Mixin's are the
>future for all languages, and I really do believe they can achieve better
>reuse and make it easier to simplify object designs that would be otherwise
>excessively complicated. That said, Smalltalk is quite a fantastic language
>without multiple inheritance, and there is a whole world of Java programmers
>that seem to think we don't need multiple inheritance *or* proper
>polymorphism. <g>
Hi Ian,
I guess I don't fully understand the difference between the
use of polymorphism to delegate behavior and mixins. Or maybe there
isn't any! Could you explain? Thanks,
--
Larry
Email to rapp at lmr dot com
| |
| Can Altinbay 2004-03-27, 12:27 am |
| "Vincent Foley" <vinfoley@iquebec.com> wrote in message
news:pan.2004.03.18.04.10.58.562501@iquebec.com...
> I found an example in Squeak where it could be useful. You have
> ReadStream class and a WriteStream class. To make up the ReadWriteStream
> class, it would probably be the natural thing to do.
>
This is the only example I know where multiple inheritance could help.
Generally, examples I have been cited have been muddled or totally off the
wall. The worst one was "pie inherits from sugar and flour". I'm not sure
what you are looking for in your Dog, Animal, Bulldog example, but it
doesn't appear to be well factored. Dog should already inherit from Animal.
| |
| Craig Latta 2004-03-27, 12:27 am |
|
Hi--
>
> This is the only example I know where multiple inheritance could help.
I find even the ReadWriteStream example dubious. I've never needed
write-only streams per se, so I just consider all streams readable, and
have specialization for writing. I can imagine situations where the
resource in question is external and doesn't allow reading (e.g., a
file), but then the object representing the resource can raise an
exception.
For an example of a Stream hierarchy with no ReadWriteStream, see
http://www.netjam.org/self/projects.../streams1g1.zip
-C
--
Craig Latta
improvisational musical informaticist
craig@netjam.org
www.netjam.org
[|] Proceed for Truth!
| |
| Darin Johnson 2004-03-27, 12:27 am |
| Vincent Foley <vinfoley@iquebec.com> writes:
> I found an example in Squeak where it could be useful. You have
> ReadStream class and a WriteStream class. To make up the ReadWriteStream
> class, it would probably be the natural thing to do.
This is the classic example. However, that problem has been solved,
and is more of concern to people creating a new Smalltalk class
hierarchy.
The cases where multiple inheritance seem more applicable are
"mixins", but that's a very vague term. Ie, "Model" could be a mixin
and it would be useful, but it would probably come at the expense of
drastically confusing the rest of the system. Multiple inheritance
is not a simple feature, and there are many ways of defining what it
really means - ie, do you search for methods depth-first or
breadth-first, and what do you do if both classes have an instance
variable of the same name, etc. There is no one right answer.
So instead the issue is bypassed by not having MI in Smalltalk.
There are other ways to cope with the problems that MI solves.
--
Darin Johnson
Where am I? In the village... What do you want? Information...
| |
| Avi Bryant 2004-03-27, 12:27 am |
| Vincent Foley <vinfoley@iquebec.com> wrote in message news:<pan.2004.03.18.04.10.58.562501@iquebec.com>...
> module Bulldog
> include Dog, Animal
> end
>
> Is there a way to do that in Smalltalk without having to use deep black
> magic?
Yes, if someone else has done the (not very) deep black magic for you.
I played around with adding Ruby-style mixins to Squeak a while ago;
you can grab what I did from
http://beta4.com/mc/mixins/Mixins-ab.8.mcz . IIRC, that class
definition would look something like
Dog <+ Animal
subclass: #Bulldog
instanceVariableNames: ''
...
It's just a demo, though - all the tools would need to be extended to
really support it.
| |
| Chris Uppal 2004-03-27, 12:28 am |
| Vincent Foley wrote:
> But I want to ask, what is your opinion on MI? I heard Ron Jeffries say
> that it's not even needed, so how do you work around it?
I'm surprised at how many people seem to think they don't need any form of MI,
since *I* find I'm hurting for the lack of it over and over again.
Mixins (not Traits, I think) are what I need, but haven't got.
In their absence my code is littered with kludges to get around the lack, or,
if you prefer, it is littered with Patterns that are symptomatic of conceptual
"inheritance" not reified as class inheritance.
The kludges/patterns I refer to:
"False Base Class" -- introducing a base class that's *only* purpose is to
allow two conceptually independent classes to share some code.
"Just Do Without" -- leaving client code with an unnaturally restricted API, so
that although two objects are conceptually very similar in some way, that
similarity is not expressed in their protocols.
"Cut and Paste Inheritance" -- sweeping code duplication (possibly
semi-automated).
"Adaptors" -- splitting the object into the "real" part that inherits from one
class, and a wrapper/adaptor that inherits from another class and knows how to
forward messages to the "real" part.
(The names, of course, aren't standard Pattern Names).
I have more than one example of all these kludges in my own code, and I can
also point to examples in the base (Dolphin) image of every one except the last
(there may be examples of the last too, but I can't think of one offhand).
-- chris
| |
| Thomas Gagné 2004-03-27, 12:28 am |
|
Chris Uppal wrote:
> <snip>
>
>I'm surprised at how many people seem to think they don't need any form of MI,
>since *I* find I'm hurting for the lack of it over and over again.
>
>
Can you give some examples?
--
..tom
remove email address' dashes for replies
opensource middleware at <http://isectd.sourceforge.net>
http://gagne.homedns.org
| |
| Can Altinbay 2004-03-27, 12:28 am |
| "Craig Latta" <craig@netjam.org> wrote in message
news:4059F846.C3D7F5DD@netjam.org...
>
> Hi--
>
>
> I find even the ReadWriteStream example dubious. I've never needed
> write-only streams per se, so I just consider all streams readable, and
> have specialization for writing. I can imagine situations where the
> resource in question is external and doesn't allow reading (e.g., a
> file), but then the object representing the resource can raise an
> exception.
>
> For an example of a Stream hierarchy with no ReadWriteStream, see
> http://www.netjam.org/self/projects.../streams1g1.zip
>
>
Good point. I *did* say "could be useful", and it is still a much better
examle than most of the other examples where MI is alleged to be necessary.
| |
| Mark Wilden 2004-03-27, 12:28 am |
| "Chris Uppal" <chris.uppal@metagnostic.REMOVE-THIS.org> wrote in message
news:XPmdnRSyZva6c8fd4p2dnA@nildram.net...
>
> "Cut and Paste Inheritance" -- sweeping code duplication (possibly
> semi-automated).
Just a little quibble on an otherwise great post: There's nothing wrong with
cutting and pasting code. Copying and pasting, however, is sinful. :)
| |
| Ian Upright 2004-03-27, 12:28 am |
| >Hi Ian,
>
> I guess I don't fully understand the difference between the
>use of polymorphism to delegate behavior and mixins. Or maybe there
>isn't any! Could you explain? Thanks,
Hi Larry,
The big difference is that in order to use polymorphism to delegate, you
often have to build this into your design up-front. If you don't, you
likely need to do some fairly heavy-lifiting to refactor your design to
support it. In some cases, you may want to just make some simple extensions
that seem very natural and correct, where a mixin would perfectly solve the
problem without copying and pasting code. Instead, with a
single-inheritance mechanisim, you might have to do some serious refactoring
to a different design to support it. This refactoring and delegation might
also include additional performance overhead that you otherwise wouldn't
incur. It also could well be debatable if it is actually a better design to
use mixins over delegation. In some circumstances, it may well be smarter
to use delegation, but if you're dealing with a very substantial framework
or a framework that is not your own or that you do not have source code for,
then the cost of refactoring the framework to use delegation could be
extremely significant if not nearly impossible. In these cases, Mixin's fit
in nicely and save the day. In other cases, Mixins may save the day even
when you consider delegation as a possible design choice, because delegation
does incur additional performance overhead by way of adding an additional
layer of indirection, whereas mixins do not.
Ian
---
http://www.upright.net/ian/
| |
| Andre Schnoor 2004-03-27, 12:28 am |
| I encountered a simple example where MI would be nice, but somehow also too
much overhead. I've got an inheritance tree that reads something like this:
Bunch (abstract)
SortedBunch (abstract)
Cluster
NamedCluster (abstract)
Chord
Scale
where there is another class 'Tonality', located elsewhere, which is
basically an aggregation of one or more Scales. Tonality responds to a
significant subset of the methods of Scale (about a dozen, frequently used
selectors, some of them inherited from the tree). More importantly, the
*senders* don't care if the receiver is a Scale or Tonality. So we have true
polymorphism here.
However, Tonality does NOT inherit the majority of the behavior (nor the
structure) from the tree above.
I believe this is a typical example, where one ends up using delegation and
a few cases of copy & paste (that's what I finally I did). When performance
is of importance, one should not bother with a purist's problems. I added a
comment to each method with a significant amout of pasted code to help
support future maintenance. I believe, that's a practical compromise.
Andre
| |
| L. M. Rappaport 2004-03-27, 12:28 am |
| On Sat, 20 Mar 2004 02:54:54 GMT, Ian Upright <ian-news@upright.net>
wrote (with possible editing):
>
>Hi Larry,
>
>The big difference is that in order to use polymorphism to delegate, you
>often have to build this into your design up-front. If you don't, you
>likely need to do some fairly heavy-lifiting to refactor your design to
>support it. In some cases, you may want to just make some simple extensions
>that seem very natural and correct, where a mixin would perfectly solve the
>problem without copying and pasting code. Instead, with a
>single-inheritance mechanisim, you might have to do some serious refactoring
>to a different design to support it. This refactoring and delegation might
>also include additional performance overhead that you otherwise wouldn't
>incur. It also could well be debatable if it is actually a better design to
>use mixins over delegation. In some circumstances, it may well be smarter
>to use delegation, but if you're dealing with a very substantial framework
>or a framework that is not your own or that you do not have source code for,
>then the cost of refactoring the framework to use delegation could be
>extremely significant if not nearly impossible. In these cases, Mixin's fit
>in nicely and save the day. In other cases, Mixins may save the day even
>when you consider delegation as a possible design choice, because delegation
>does incur additional performance overhead by way of adding an additional
>layer of indirection, whereas mixins do not.
>
>Ian
Hi Ian,
Thanks for the explanation. I am years away from C++ which
used them, but in Smalltalk, wouldn't mixins have to have access to
the instance variables of an object in order to be effective? Or
would they just be pluggable behaviors which return something which
gets assigned directly?
--
Larry
Email to rapp at lmr dot com
| |
| Stefan Schmiedl 2004-03-27, 12:28 am |
| On Sat, 20 Mar 2004 14:25:36 GMT,
L M Rappaport <nospam@invalid.org> wrote:
>
> Hi Ian,
>
> Thanks for the explanation. I am years away from C++ which
> used them, but in Smalltalk, wouldn't mixins have to have access to
> the instance variables of an object in order to be effective? Or
> would they just be pluggable behaviors which return something which
> gets assigned directly?
Ruby handles mixins roughly in the following way: A (mixin) Module
introduces an anomyous superclass of the class it gets mixed into.
So modules are like "class factories" there.
Example: Define "each" in your class, mixin "Enumerable", you get
"select", "inject" etc. for free.
The point is, a Module does not have access to instance variables
of its "derived" classes. All it does is provide "generic"
implementations based upon a special implementation of a few methods.
s.
| |
| Lex Spoon 2004-03-27, 12:28 am |
| "Andre Schnoor" <andre.schnoor@web.de> writes:
> Bunch (abstract)
> SortedBunch (abstract)
> Cluster
> NamedCluster (abstract)
> Chord
> Scale
>
> where there is another class 'Tonality', located elsewhere, which is
> basically an aggregation of one or more Scales. Tonality responds to a
> significant subset of the methods of Scale (about a dozen, frequently used
> selectors, some of them inherited from the tree). More importantly, the
> *senders* don't care if the receiver is a Scale or Tonality. So we have true
> polymorphism here.
>
> However, Tonality does NOT inherit the majority of the behavior (nor the
> structure) from the tree above.
Alternatively, methods can be moved up the hierarchy, and simply fail
when called on inappropriate subclasses. Witness class Collection.
Or, you can have callers request a specific role from the object:
myScaleOrTonality asScale doScalishThing
asScale returns self for Scale's, and returns a wrapper for
Tonalities. In practice, asScale is probably not the best name; there
is probably a name for the behavior that is common between the two
classes.
Now, I'm not saying either of these solutions is beautiful. However,
they do give you a clean factoring that uses only single inheritance.
Further, they all avoid copy and paste (though not necessarily cut and
paste :)).
As another example of this situation you describe, Squeak's Morph
versus Rectangle comes to mind. They all have top, left, center,
bottom, etc. etc. In this case the problem could be solved by making
people write "morph bound left" instead of "morph left", but that has
been deemed too much verbiage for a class intended for kids to us.
-Lex
| |
| L. M. Rappaport 2004-03-27, 12:28 am |
| On Fri, 19 Mar 2004 12:54:24 -0000, "Chris Uppal"
<chris.uppal@metagnostic.REMOVE-THIS.org> wrote (with possible
editing):
>Vincent Foley wrote:
>
>
>I'm surprised at how many people seem to think they don't need any form of MI,
>since *I* find I'm hurting for the lack of it over and over again.
>
>Mixins (not Traits, I think) are what I need, but haven't got.
>
>In their absence my code is littered with kludges to get around the lack, or,
>if you prefer, it is littered with Patterns that are symptomatic of conceptual
>"inheritance" not reified as class inheritance.
>
>The kludges/patterns I refer to:
>
>"False Base Class" -- introducing a base class that's *only* purpose is to
>allow two conceptually independent classes to share some code.
>
>"Just Do Without" -- leaving client code with an unnaturally restricted API, so
>that although two objects are conceptually very similar in some way, that
>similarity is not expressed in their protocols.
>
>"Cut and Paste Inheritance" -- sweeping code duplication (possibly
>semi-automated).
>
>"Adaptors" -- splitting the object into the "real" part that inherits from one
>class, and a wrapper/adaptor that inherits from another class and knows how to
>forward messages to the "real" part.
>
>(The names, of course, aren't standard Pattern Names).
>
>I have more than one example of all these kludges in my own code, and I can
>also point to examples in the base (Dolphin) image of every one except the last
>(there may be examples of the last too, but I can't think of one offhand).
>
> -- chris
Hi Chris,
A question. Given that it tends to complicate inheritance,
can't a lot of your patterns above be eliminated and replaced by a
state machine? Seems like that allows for differing behavior with
similar structure where inheritance breaks down because of differing
characteristics in the logical trees.
Just a thought.
--
Larry
Email to rapp at lmr dot com
| |
| Darin Johnson 2004-03-27, 12:28 am |
| L. M. Rappaport <nospam@invalid.org> writes:
> Thanks for the explanation. I am years away from C++ which
> used them, but in Smalltalk, wouldn't mixins have to have access to
> the instance variables of an object in order to be effective?
Depends upon how you define "mixin". There's definately a subset
of mixins that are standalone or only require access to well known
methods.
The original term was borrowed from an ice-cream store near a
university (MIT I think). The classes were called "flavors", and a
"mixin" could be added to any flavor to modify it. So a mixin that
depended upon access to instance variables would not be very versatile
across a wide range of classes.
--
Darin Johnson
"Floyd here now!"
| |
| Randy A. Ynchausti 2004-03-27, 12:28 am |
| Chris
>
> I'm surprised at how many people seem to think they don't need any form of
MI,
> since *I* find I'm hurting for the lack of it over and over again.
I understand what you mean. However, I should note that there is often
tremendous complexity in resolving the possible ambiguity that can result
from multiple inheritance. Implementing the work-arounds that have been
noted is often less complex than assessing and dealing with multiple
inheritance ambiguity. Obviously there are ways to create framework
architectures in Smalltalk for handling multiple inheritance, but no one
that I know of has spent their money to build a generic one. If someone did
build a simple framework for multiple inheritance, I would use it --
probably very infrequently though.
Regards,
Randy
| |
| Ian Upright 2004-03-27, 12:28 am |
| Larry writes:
> Thanks for the explanation. I am years away from C++ which
>used them, but in Smalltalk, wouldn't mixins have to have access to
>the instance variables of an object in order to be effective?
Absoolutely, Larry. With the way S# implements Mixins, Mixins themselves
can even declare their own dynamic instance variables. S# directly supports
dynamic or property instance variables, which are like a property dictionary
associated with an object. However, real instance variables (slots) can
preside over property instance variables, so the method of accessing the
instance variables is determined by way of the class structure definition.
This seems like one of the best ways to implement Mixins I've seen.
Ian
---
http://www.upright.net/ian/
| |
| Ian Upright 2004-03-27, 12:28 am |
| Lex Spoon <lex@cc.gatech.edu> wrote:
>Alternatively, methods can be moved up the hierarchy, and simply fail
>when called on inappropriate subclasses. Witness class Collection.
>
>Or, you can have callers request a specific role from the object:
>
> myScaleOrTonality asScale doScalishThing
>
>asScale returns self for Scale's, and returns a wrapper for
>Tonalities. In practice, asScale is probably not the best name; there
>is probably a name for the behavior that is common between the two
>classes.
>
>Now, I'm not saying either of these solutions is beautiful. However,
>they do give you a clean factoring that uses only single inheritance.
>Further, they all avoid copy and paste (though not necessarily cut and
>paste :)).
Absolutely.. No matter what design you choose, their is always a solution
(hack?) around *not* copying and pasting. Copying and pasting IMO is the
greatest immortal sin, and one that I've never actually needed to commit in
my 8+ years of Smalltalk programming. But I'm of the belief, no matter how
bad the hack is, the hack is always better than copying and pasting. :)
Mixins can provide a good solution to avoid implementing some of these
hacks. Multimethods can as well, which is another paradigm that I think is
useful. Why close off our minds? Use the best tool for the job! :)
Ian
---
http://www.upright.net/ian/
| |
| Volker Zink 2004-03-27, 12:28 am |
| The case where i really would like to have MI or Mixins is Persistency.
Normally the persistency framework is some given piece of software
and persistent objects must be subclasses of some Base class. Normally
i have module-specific Base classes from which persistent and
non-persistent classes should inherit, which is not possible.
Because its not a trivial task to modify the persistency framework
i normally use suboptimal solutions.
Volker
Chris Uppal schrieb:
> Vincent Foley wrote:
>
>
>
>
> I'm surprised at how many people seem to think they don't need any form of MI,
> since *I* find I'm hurting for the lack of it over and over again.
>
> Mixins (not Traits, I think) are what I need, but haven't got.
>
> In their absence my code is littered with kludges to get around the lack, or,
> if you prefer, it is littered with Patterns that are symptomatic of conceptual
> "inheritance" not reified as class inheritance.
>
> The kludges/patterns I refer to:
>
> "False Base Class" -- introducing a base class that's *only* purpose is to
> allow two conceptually independent classes to share some code.
>
> "Just Do Without" -- leaving client code with an unnaturally restricted API, so
> that although two objects are conceptually very similar in some way, that
> similarity is not expressed in their protocols.
>
> "Cut and Paste Inheritance" -- sweeping code duplication (possibly
> semi-automated).
>
> "Adaptors" -- splitting the object into the "real" part that inherits from one
> class, and a wrapper/adaptor that inherits from another class and knows how to
> forward messages to the "real" part.
>
> (The names, of course, aren't standard Pattern Names).
>
> I have more than one example of all these kludges in my own code, and I can
> also point to examples in the base (Dolphin) image of every one except the last
> (there may be examples of the last too, but I can't think of one offhand).
>
> -- chris
>
>
| |
| Thomas Gagné 2004-03-27, 12:28 am |
|
Volker Zink wrote:
> The case where i really would like to have MI or Mixins is Persistency.
> Normally the persistency framework is some given piece of software
> and persistent objects must be subclasses of some Base class. Normally
> i have module-specific Base classes from which persistent and
> non-persistent classes should inherit, which is not possible.
> Because its not a trivial task to modify the persistency framework
> i normally use suboptimal solutions.
>
Which framework is that? Gemstone extended Object with persistency so
hierarchies didn't need to change or require convolutions. It's an OODB
worth looking into. Is your's something developed in-house?
--
..tom
remove email address' dashes for replies
opensource middleware at <http://isectd.sourceforge.net>
<http://gagne.homedns.org/~tgagne/>
| |
| Victor Metelitsa 2004-03-27, 12:28 am |
| Volker Zink wrote:
> The case where i really would like to have MI or Mixins is Persistency.
> Normally the persistency framework is some given piece of software
> and persistent objects must be subclasses of some Base class.
It's wrong for TOPLink/S and Glorp.
> Normally
> i have module-specific Base classes from which persistent and
> non-persistent classes should inherit, which is not possible.
> Because its not a trivial task to modify the persistency framework
> i normally use suboptimal solutions.
>
[...]
| |
| Andre Schnoor 2004-03-27, 12:28 am |
| >>>Copying and pasting IMO is the greatest immortal sin, and one that
I've never actually needed to commit in my 8+ years of Smalltalk
programming. But I'm of the belief, no matter how bad the hack is,
the hack is always better than copying and pasting. :)<<<
Depends on what is actually considered "copy & paste". Copying simple
accessors with no further semantics in them does not fall into this
cateory, I think. Even simple three-liners with a trivial guarding clause
are not
worth the effort of building something sophisticated around them just
to do it 100% right. I've seen classes which were totally "overdone", with
lots of elegant stuff that caused the simplest messages to pass through
a handful of other classes.
Polymorphy is always a tradeoff. If you do it "to the max", you get an
almost
unreadable source, because even simple functionality is spread over dozens
of
classes, each of which is doing a little something that can not be
understood
without spending hours of browsing their senders & implementers, etc.
I many cases, doing it 100% clean means just adding complexity and is not
neccessary, as long as those spots are clearly identified and commented.
BTW: Object composition is a clean solution for MI in many cases. If an
object should inherit from two classes, it can be composed of two seperate
objects, where messages are delegated to. This is clean *and* efficient.
So, if you want a "persistent customer" for example, you just compose
Customer and PersistentObject (or whatever). The composed Object functions
as kind of a wrapper, delegating messages to either component.
Example: I needed a local cache for computation results for a lot of objects
at
very different places in the global hierarchy. I implemented the class
LocalCache,
which handles flushing, MRU lookup etc. Other classes use LocalCache as one
of their instance variables, should they need caching.
For external behavior, however, you need to add individual selectors to the
parent.
But, hey, isn't that what we are doing all the time anyway: Adding a
selector for
each and every tiny bit of functionality?
Andre
| |
| Ian Upright 2004-03-27, 12:28 am |
| Volker Zink <Volker.Zink@porabo.ch> wrote:
>The case where i really would like to have MI or Mixins is Persistency.
>Normally the persistency framework is some given piece of software
>and persistent objects must be subclasses of some Base class. Normally
>i have module-specific Base classes from which persistent and
>non-persistent classes should inherit, which is not possible.
>Because its not a trivial task to modify the persistency framework
>i normally use suboptimal solutions.
>
>Volker
In Smalltalk, this shouldn't be needed.. The functionality for persistence
can be placed right in Object iself. However, there is a lot of semantics
around this that could certainly clean things up.. but persistence strangely
enough I don't think is where it is all that important to have MI. The
collection class heirarchy though, I think is a different story.
Ian
---
http://www.upright.net/ian/
| |
| Ian Upright 2004-03-27, 12:28 am |
| Hi Andre,
>Polymorphy is always a tradeoff. If you do it "to the max", you get an
>almost
>unreadable source, because even simple functionality is spread over dozens
>of
>classes, each of which is doing a little something that can not be
>understood
>without spending hours of browsing their senders & implementers, etc.
>
>I many cases, doing it 100% clean means just adding complexity and is not
>neccessary, as long as those spots are clearly identified and commented.
Agreed.. I guess my implication is copying & pasting any kind of
significant behavior, such as taking a 15-line method, copying it, and
tweaking it very slightly to call a different method somewhere down in it's
implementation.
>BTW: Object composition is a clean solution for MI in many cases. If an
>object should inherit from two classes, it can be composed of two seperate
>objects, where messages are delegated to. This is clean *and* efficient.
>
>So, if you want a "persistent customer" for example, you just compose
>Customer and PersistentObject (or whatever). The composed Object functions
>as kind of a wrapper, delegating messages to either component.
Persistence is one area that should be totally transparent, and I don't
believe it has any place in the Object heirarchy. I think the same should
go for distributed computing/communication.
>Example: I needed a local cache for computation results for a lot of objects
>at
>very different places in the global hierarchy. I implemented the class
>LocalCache,
>which handles flushing, MRU lookup etc. Other classes use LocalCache as one
>of their instance variables, should they need caching.
>
>For external behavior, however, you need to add individual selectors to the
>parent.
>But, hey, isn't that what we are doing all the time anyway: Adding a
>selector for
>each and every tiny bit of functionality?
Well with traditional Smalltalk usually I use tools or frameworks that
auto-generate such things, and with S# accessors can be implicitly
auto-generated (such that it acts like the accessors are there, even though
the methods themselves aren't declared anywhere).
Ian
---
http://www.upright.net/ian/
| |
| Volker Zink 2004-03-27, 12:28 am |
| Persistency should be a service to use, so MI should be no issue for
persistency. But i worked with some persistency frameworks which
used base classes (some of them written by members of a project and
therfore were only used in this project; a more well known was that
from Mikado which also used a base class if i remember it correct).
In my opinion its not a matter if MI is NEEDED, but if it does improve
coding. And it does improve coding if it eases solutions and if it has
no drawbacks (complexity of code, ...). So if there is such a thing,
build it and i will use it.
Volker
Ian Upright schrieb:
> Volker Zink <Volker.Zink@porabo.ch> wrote:
>
>
>
>
> In Smalltalk, this shouldn't be needed.. The functionality for persistence
> can be placed right in Object iself. However, there is a lot of semantics
> around this that could certainly clean things up.. but persistence strangely
> enough I don't think is where it is all that important to have MI. The
> collection class heirarchy though, I think is a different story.
>
> Ian
>
> ---
> http://www.upright.net/ian/
| |
| Chris Uppal 2004-03-27, 12:28 am |
| L. M. Rappaport wrote:
> A question. Given that it tends to complicate inheritance,
> can't a lot of your patterns above be eliminated and replaced by a
> state machine?
I don't see how it would help ? Given that there is some behavior, B, that I
want to have shared by instances of classes A and B, where would the state
machine go, what would it do ?
I'm sorry if I'm being obtuse, but I just don't understand what you mean.
-- chris
| |
| Chris Uppal 2004-03-27, 12:28 am |
| Randy A. Ynchausti wrote:
>
> I understand what you mean. However, I should note that there is often
> tremendous complexity in resolving the possible ambiguity that can result
> from multiple inheritance.
I don't think mixins do introduce ambiguity do they ? (Assuming that they
aren't allowed to use duplicate instvar names, or at least that the language is
able to deal with such duplicates).
In fact -- for me -- mixins don't feel like multiple inheritance at all.
Indeed they don't really feel like inheritance -- inheritance always seems to
bring with it a possibility of subtype/ISA relationships (even when that is not
intended), and mixins seem to side-step that baggage.
I like the Bracha formulation of a mixin as a rule/operator for producing new
(sub)classes from existing classes. That seems to fit exactly with how I (want
to) think of Mixins as a way of specifying shared behaviour independently of
the class hierarchy (as opposed to: "a way of specifying shared behaviour by
making the class hierarchy richer (but more complicated)", which is how I would
describe MI).
-- chris
| |
| Chris Uppal 2004-03-27, 12:28 am |
| Thomas Gagné wrote:
> Can you give some examples?
I think so... ;-)
Example of cut and paste (OK, "copy and paste" -- thanks Mark ;-)
One Dolphin UI component, ListView, comes in two flavours with mostly similar
behaviour. They are represented as two classes (and there's an excellent third
party goodie that adds yet a third class). I want to use these components as a
basis for my own component (ListTreeView) that adds quite a lot of complicated
behaviour. Hence I have three kinds of ListTreeView that each are subclassed
from one of the flavours of ListView. That involves duplicating some 200
methods (mostly very small) in each subclass; in fact there are only one or two
methods that are *not* duplicated (although that's partly because I
deliberately, and somewhat artificially, wrote it to minimise "special" code
that couldn't be copied automatically).
Example of being forced into using adaptors.
One of my things allows you to refer to Java objects from Smalltalk. The Java
objects are represented by Smalltalk proxies that "know" how to invoke the real
Java object's behaviours. The proxies (being part of a complicated system)
must be derived from specific base classes in my framework. OTOH, I want to
make the proxies work as much like normal Smalltalk objects as possible. E.g.
I want to be able to use #do: to iterate over the elements of a Java array.
Now just defining a #do: method is easy, but I also want to pick up any other
methods that <Array>s should respond to (#first, #second and #third, for
example, or #do:separatedBy:). So what I've been forced to do is define
(completely unwanted) adaptors that are subclassed from the appropriate spots
under Collection, and which forward the "base" methods to one of the real
proxies. This adds complexity and inconsistency to the client API.
Example of "just do without".
A similar "problem" occurs with Dolphin's in-the-box ability to handle
externally defined structures. E.g. an array of SOMEWINDOWSSTRUCTs returned by
some Windows API. These things are (conceptually) arrays, and OA have chosen
to duplicate much, but not all, of the protocol of Smalltalk arrays. Most of
the protocol that they have duplicated falls into the "copy and paste" pattern,
the bits they have missed fall into the "just do without".
I won't bother you with an example the "false base class" kludge. On
reflection, although such cases are certainly examples of why I want mixins,
they aren't really about *multiple* inheritance so much as about sharing
behaviour *without* inheritance.
It occurs to me that most of the examples I can think of (not all) seem to crop
up in cases where an object has two roles at *different levels* of the system.
Where they have one role to play as part of the implementation of a system, and
another to play as part of the purpose of the system. Volka Zink's example of
persistence seems to follow that pattern too.
-- chris
| |
| Chris Uppal 2004-03-27, 12:28 am |
| Ian Upright wrote:
> Agreed.. I guess my implication is copying & pasting any kind of
> significant behavior, such as taking a 15-line method [...]
Or 15 3-line methods...
-- chris
| |
| Chris Uppal 2004-03-27, 12:28 am |
| Ian Upright wrote:
> In Smalltalk, this shouldn't be needed.. The functionality for
> persistence can be placed right in Object iself.
That suggests a style of programming where *everything* is defined against
Object ;-)
I wonder what that'd be like, maybe not as bad (given appropriate tool &
browser support) as it sounds...
-- chris
| |
| Mark Wilden 2004-03-27, 12:28 am |
| "Chris Uppal" <chris.uppal@metagnostic.REMOVE-THIS.org> wrote in message
news:87qdneglMeVh0P3dRVn-gw@nildram.net...
> Randy A. Ynchausti wrote:
>
form[color=darkred]
result[color=darkred]
>
> I don't think mixins do introduce ambiguity do they ? (Assuming that they
> aren't allowed to use duplicate instvar names, or at least that the
language is
> able to deal with such duplicates).
Indeed, the whole "ambiguity" issue is a red herring. In C++, for example,
there are strict rules concerning dominance. Understanding these rules isn't
harder than anything else in C++. :)
| |
| David Simmons 2004-03-27, 12:28 am |
| I think that the complexity/problems associated with MI are largely baggage
from static and statically typed languages. For almost all intents and
purposes Smalltalk is pure-encapsulation through messaging language.
The issues of "MI" are different in a procedure based language with static
binding rules, than they are for a dynamically bound language with strict
"logical" encpasulation of access control to structure.
In the statically bound language category MI centers around issues of static
binding to the physical layout/representation of object structure (which
does indeed become a real problem for MI).
In a language where direct access to structure/layout is "logically"
encapsulated (i.e., most dynamic languages) this problem does not (need to)
exist.
I think that is one reasons why the terminology that evolved in those two
distinct language categories has generally been a bit different. I.e.,
"multiple inheritance" versus "mixins". Within S# you'll see a variety of
related names for the concepts behind mixins (i.e., aspect, role, facet,
mixin, specification, interface, etc).
Personally I see this area as a subset of the general case of (software)
composition of control-points. This is an area that I spent a lot of mental
energy on when I originally designed QKS Smalltalk in 1990-1992. At that
time one of the problems I was really interested in addressing was
distributed objects, persistency and object oriented databases. By that time
my professional work had involved distributed systems, databases, and legacy
system integration to a server/workstation/desktop for almost ten years.
I designed the QKS VM platform (which we originally called a backplane --
which should also help date the thinking <g> ) to directly support control
points for binding, slot/struct read/write barriers, object state
transformations, and execution flow within and between threads. Those things
have stayed with all my virtual machine designs since then, even though I
generally don't talk about them very much.
The above is my longwinded way of saying that I really prefer describing
this area using the terms "weaving", "composition", and "mixins" (in that
order).
Having said that, the critical issue in any compositional system is to
ensure that behavior of the system will be deterministic. In such a system I
believe it is equally important that a develeoper/designer introducing
(weaving) a participating element into such a system can declaratively
express their desired deterministic behavior.
In the case of S#, the declarative layer is built upon the dynamic layer and
not the other way around. That enables the entire system to support
consistent dynamic language characteristics/capabilities to its core.
The mixin model is best described (at the internal level) as being a
inheritance mixin of behavior (methods), and an aggregation model of
structure (physical slot/struct fields). [For those who may not be aware, S#
supports (non-object) struct/value-type fields just like
Pascal/C/C++/Java/C# etc)].
There are basically three categories of "mixins".
a) Specifications
b) PureMixins
c) Interfaces (ala COM)
A <Specification> (within S#) is conceptually the same as what "Java" calls
an interface [while it also offers some additional dynamic/reflecive
characteristics]. The methods within a specification (or any method marked
as a specification method) will bind last among all inherited methods, and
if bound and executed it will throw a <ImplementationResponsibility>
exception.
In Java, an interface is basically a requirement/promise that the developer
of the class will "have to write" a bunch of methods on their class
implementation that conform to the "type-signature and binding
characteristics". Which, personally (in the case of Java interfaces), I find
to be a bit of an anathema that flies in the face of reuse and good design.
I.e., Java took a good step forward and then two steps back.
A <PureMixin> (within S#) is one where the mixin itself defines no concrete
structure/storage locations. Its structural superclasses also do not define
any. All its methods are therefore purely behavioral. It can however declare
virtual fields and declare them as property fields. A field declaration
allows the developer to use the field's name directly within method
expressions.
When the compiler/jit encounters a reference to a property field it will
issue a "self field_name" or "self field_name: value" message unless the
method in which the expression appears is the "getter/setter" for the field.
When the compiler/jit determines a direct read/write is appropriate and the
field is a virtual field, it will issue a "self virtualFieldValueAt:/put:"
message. The latter can then be implemented as desired by the object (or its
manager if the object instance's binding circuitry [control-point] has been
re-routed).
The default implementation uses the extensible properties feature of the AOS
(S#'s virtual machine) Execution Engine object model. Basically within the
AOS object model, all objects can be completely restructured [at pretty much
zero cost] while retaining their object identity. One of the key mechanisms
is the ability to add (ore remove) arbitrary fields or collections to an
object [which is not as efficient as describing them in a classes field
layout]. Thus the default virtualFieldAt:/put: methods issue calls to "self
propertyType: nil at: field_name/put: value". Which in turn defaults to
"self basicProperties type: any_object at: selector put: value". The dual
layering allows frameworks or individual object managers (often used for
persistency/oodb/orb/distributed-objects) to manage the property system
themselves. The UI framework makes extensive use of this. All objects use
this infrastructure to support delegation and it is incoroprated in the
default handler system for DNR/DNU (does not respond to). Which (taking a
sidebar here for a moment), allows one to write:
some_object when: #foo do: ['Hi' alert].
some_object foo. "will invoke the block"
Talking more about this and "event methods" would be another topic --
although it plays closely with issues of code weaving.
An <Interface> (within S#) is a concrete mixin which can define slots and
struct fields. It can also be instantiated to obtain a discrete object that
represents a facet/aspect of some core-personality object. This makes them
capable of exhibiting the same behavior as a COM interface. When an
interface is instantiated it is bound to the core-personality object it is
associated with -- this is the (internals) "aggregation" action mentioned
earlier.
Interfaces are only instantiated when explicitly requested, or when the
jit/runtime determines it is necessary to have a physical field location to
write to. Which is my way of saying that just because you declared an
interface does not mean it will ever be instantiated; and it certainl will
not be instantiated until it is really needed.
Because interfaces can be instantiated they also provide their own
encapsulation from direct access to other aspects (interfaces) of their
core-personality. You can therefore define methods which can only be
accessed from an interface instance, or vice-versa from the
core-personality. Given an interface instance, one can always obtain the
core-personality or some other interface on the personality. Interfaces
support COM protocol directly down to the JIT and VM machinery as well so
using them basically gives you free COM behavior as well. This maps over
into .NET facilities, but that is also a whole other discussion.
Within an interface method, <self> always refers to the core-personality
object. To reference the interface itself you use the pseudo-variable
<interface>. To obtain an interface from an object you simply write:
some_object as: SomeInterface. "the dynamic equivalent of a cast"
-----
Some other relevant characteristics to note:
a) Classes also have classes (i.e., a metaclass). Therefore mixins can be
separately declared-for/applied-to both a classes instances and a
metaclasses instances (i.e., the class itself). Mixins can also be mixed in
on other mixins which enables rich declarative behavior latices for reducing
a complex object behavior into a single mixin representing its contract.
b) Virtual and property field characteristics are fundamental to enabling
consistent patterns of code writing style. As is the semantics of <self>
within interface methods. Conceptually, for some people, it can be useful to
think of mixin methods as macros that the jit will expand as appropriate for
a given target/receiver object.
c) Mixins promote re-use and enabling extending the pure-class-inheritance
model to a more compositional weaving practice where appropriate.
d) Mixins are considered to be supertypes, not superclasses within the S#
model. The reflection protocol supports this distinction enabling one to ask
distinct questions about both. Classes and metaclasses are subclasses of
<Constructor> which describe contiguous physical layout/structure. The
#isKindOf: method interrogates supertypes not just superclasses. The S#
multi-method dispatch binding predicate system is based on supertypes not
just superclasses.
e) Internally, the AOS object model supports prototype behavior (not just
type/class based behavior). Thus one can apply mixins to individual objects
at will.
f) A given class can both override/extend any mixins behavior, and it can
scope that behavior so it is only accessible from a given caller/caller
access group. This is significant for both eliminating issues of mixin
namespace collision and for transparent (zero-execution-cost) creation of
foreign function marshalling systems for COM and cross language jitting for
..NET assembly generation.
g) Finally, because it is truly a dynamic language system, one can add and
remove mixins at any time. The cost for this is very low -- although it can
incur some level of binding cache cost by lazily marking some things as
invalid.
-----
When are mixins useful?
Pretty much any place where you can describe a contract or pattern of
behavior that contains reusable behavior. Mixins are a tremendous aid to
refactoring and promotion of reuse because they are essentially a single
control point for encapsulation of a contract's implementation.
When I wanted to implement the Win32 model for native resource types, I
turned to mixins to save me a lot of implementation time, simplify the
design, and improve overall performance. Good design required that the
different types of resources needed to live in very different (discrete)
superclass hierarchies. Specifically, hierarchies that included other
objects which were NOT resources. Yet, each of the resource objects shared
almost identical common behavior even though they had different physical
structure and behavior in their other "aspects/roles".
This led to the use of the following mixins:
<IMResourceElement>, <GDIHandleObject>
[Note: I am glosssing over the fact that resources can also come from a
(think modularized smalltalk images) module's repository as well as from the
operating system or dll/coff files].
The resource element mixin handles the layered resource caching system
(effectively a (gc) cached file system for resources based on namespace,
type, and module).
The gdi-handle object mixin handles the Win32 OS requirements for gdi
objects and weak-reference gc-able object <-> handle mapping caches.
There are only 13 methods within the <GDIHandleObject> control point.
There are only 8 methods within the <IMResourceElement> control point.
Collectively, they are utilized (referenced) within 18 or so
classes/metaclass declarations. That is a great return on investment in
terms of reuse. It is also a great complexity manager and design stability
control point because only one place needs to be updated if change is ever
required. Had this been Java, there would have definitely been a
combinatoric explosion of method implementations. Even in classic Smalltalk
with a more flexible ad-hoc polymorphic type system we would have seen a
very significant expansion in the number of "duplicate" methods that needed
to be written (or we would have seen base classes with methods that were
innapropriate/innapplicable for various subclasses).
[Another sidebar -- S# allows uninheritance of behavior through blocking
methods -- specifically to aid in composition issues with legacy or cross
language designs. Mixins can be used to contain any arbitrary set of
behavior that one might want to weave into a (or remove) from a class or
object].
There are many other opportunities for using mixins, and the S# framework is
still a long way from making full use of them as an evolution from its SI
framework origins in classic. I look forward to the day when collection
protocols, stream protocols, graphics features, persistency, etc frameworks
are built on a mature mixin weave.
-- Dave S. [www.smallscript.org]
"Chris Uppal" <chris.uppal@metagnostic.REMOVE-THIS.org> wrote in message
news:87qdneglMeVh0P3dRVn-gw@nildram.net...
> Randy A. Ynchausti wrote:
>
form[color=darkred]
result[color=darkred]
>
> I don't think mixins do introduce ambiguity do they ? (Assuming that they
> aren't allowed to use duplicate instvar names, or at least that the
language is
> able to deal with such duplicates).
>
> In fact -- for me -- mixins don't feel like multiple inheritance at all.
> Indeed they don't really feel like inheritance -- inheritance always seems
to
> bring with it a possibility of subtype/ISA relationships (even when that
is not
> intended), and mixins seem to side-step that baggage.
>
> I like the Bracha formulation of a mixin as a rule/operator for producing
new
> (sub)classes from existing classes. That seems to fit exactly with how I
(want
> to) think of Mixins as a way of specifying shared behaviour independently
of
> the class hierarchy (as opposed to: "a way of specifying shared behaviour
by
> making the class hierarchy richer (but more complicated)", which is how I
would
> describe MI).
>
> -- chris
>
>
| |
| Darin Johnson 2004-03-27, 12:28 am |
| "Mark Wilden" <mark@mwilden.com> writes:
> Indeed, the whole "ambiguity" issue is a red herring. In C++, for example,
> there are strict rules concerning dominance. Understanding these rules isn't
> harder than anything else in C++. :)
Maybe the ambiguity is a red herring, but the confusion from not
knowing exactly what the rules are is still a problem, and the rules
necessary to get rid of the ambiguity can be complex.
When implementing or using multiple inheritance, one must decide
what happens in these situations (and there is no one right answer
to any of these!):
- More than one branch of the inheritance tree defines
the same message; ie, breadth first, depth first, or
some hybrid.
- Which branch handles doesNotUnderstand:? Given that
Object defines this method, can a mixin ever override
it effectively if depth first method searching is used?
- Can a base class or mixin access variables that aren't in
it's strict hierarchy (assuming there are no automatic
accessors for instance variables)?
- How is the "diamond" inheritance pattern resolved? Ie, if
two base classes share a common ancestor. If the common
ancestor declares an instance variable, are there two copies
of it in the final class, or one shared copy?
And more besides these...
The last one is interesting, because C++ didn't make a firm decision
on it, but instead the programmer can use the "virtual" keyword to
change the multiple inheritance behavior. (this sort of choice shows
up a lot in C++, where the flexibility that allows extra optimization
also introduces new complexity)
So while a language may not be ambiguous, there are still a lot of
rules the programmer has to keep straight. Restricting one's self
to "mixins" (an ambiguous term) is a common solution in that it
bypasses many of the rules so that there aren't a lot of unexpected
surprises.
--
Darin Johnson
Where am I? In the village... What do you want? Information...
| |
| Mark Wilden 2004-03-27, 12:28 am |
| "Darin Johnson" <darin_@_usa_._net> wrote in message
news:cu1d673f24m.fsf@nokia.com...
> So while a language may not be ambiguous, there are still a lot of
> rules the programmer has to keep straight.
In theory, yes; but in practice, it's not really an issue. I've been using
C++ for 14 years. There are tougher things than learning its MI rules,
believe me. :)
| |
| David Simmons 2004-03-27, 12:28 am |
| "Darin Johnson" <darin_@_usa_._net> wrote in message
news:cu1d673f24m.fsf@nokia.com...
> "Mark Wilden" <mark@mwilden.com> writes:
>
example,[color=darkred]
isn't[color=darkred]
>
> Maybe the ambiguity is a red herring, but the confusion from not
> knowing exactly what the rules are is still a problem, and the rules
> necessary to get rid of the ambiguity can be complex.
>
> When implementing or using multiple inheritance, one must decide
> what happens in these situations (and there is no one right answer
> to any of these!):
> - More than one branch of the inheritance tree defines
> the same message; ie, breadth first, depth first, or
> some hybrid.
FYI, regarding how this is implemented in S#'s mixin system.
Binding order is based on mixin declaration order [which in a dynamic system
maps down to the order in which a mixin was added to a given behavior].
However, if a supertype appears after a subtype then the ordering will be
computed to ensure that the subtype appears ahead of the subtype.
Exact ordering can always we interrogated reflectively by asking a class for
its supertypes.
What actually happens is that the VM binding machinery itself only processes
a linearized list of method dictionaries. If that list is <nil> it will
recompute it. Anytime the inheritance ordering for a class is changed the
linearized list must be recomputed using a deterministic algorithm.
> - Which branch handles doesNotUnderstand:? Given that
> Object defines this method, can a mixin ever override
> it effectively if depth first method searching is used?
In S# mixins, it is based on the first (valid) implementation to be
encountered within the linearized list of method dictionaries for a given
objects <Behavior> (aka the behavior's supertypes list).
> - Can a base class or mixin access variables that aren't in
> it's strict hierarchy (assuming there are no automatic
> accessors for instance variables)?
In S#, as in Smalltalk, variable access is encapsulated to the superclass
constructor hierarchy for a class. A class can only have one "superclass",
but many mixin supertypes. Mixins are inherited from superclasses. S#
supports scoped message (selector namespaces) so even accessors can be made
private or restricted to the supertype family.
A mixin never has direct read/write access to a field, it must invoke an
accessor method. It is the jit's job to optimize away such references (as
appropriate to the runtime metadata state).
> - How is the "diamond" inheritance pattern resolved? Ie, if
> two base classes share a common ancestor. If the common
> ancestor declares an instance variable, are there two copies
> of it in the final class, or one shared copy?
In S# model, as mentioned in another post, only <Interfaces> can have
concrete fields. There is never more than one instance of an interface
associated with a given object that has declared that interface as a mixin
somewhere in it supertype hierarchy.
I.e., there is always only one shared copy -- this is the internal (COM
style) aggregation model.
The diamond inheritance pattern, as with S# namespaces, is strictly resolved
during linearization by forcing sub-nodes to appear before super-nodes
within the declared supertype tree. The linearization algorithm is the same
for both (superscopes) namespaces and (supertypes) inheritance.
The algorithm for computing this is a simple recursion routine that runs
very quickly -- it is built into the VM to avoid the chicken-and-egg problem
thereby enabling computation during initial method lookup.
-- Dave S. [www.smallscript.org]
> And more besides these...
>
> The last one is interesting, because C++ didn't make a firm decision
> on it, but instead the programmer can use the "virtual" keyword to
> change the multiple inheritance behavior. (this sort of choice shows
> up a lot in C++, where the flexibility that allows extra optimization
> also introduces new complexity)
>
> So while a language may not be ambiguous, there are still a lot of
> rules the programmer has to keep straight. Restricting one's self
> to "mixins" (an ambiguous term) is a common solution in that it
> bypasses many of the rules so that there aren't a lot of unexpected
> surprises.
>
> --
> Darin Johnson
> Where am I? In the village... What do you want? Information...
| |
| Randy A. Ynchausti 2004-03-27, 12:28 am |
| Mark,
>
> In theory, yes; but in practice, it's not really an issue. I've been using
> C++ for 14 years. There are tougher things than learning its MI rules,
> believe me. :)
1) Resolving the ambiguity is the reason for the MI rules.
2) If there was no ambiguity, there would be no reason to have MI rules.
3) If it wasn't an issue in practice, we wouldn't need the MI rules.
4) We have the MI rules.
Regards,
Randy
| |
| Mark Wilden 2004-03-27, 12:28 am |
| "Randy A. Ynchausti" <randy_ynchausti@msn.com> wrote in message
news:40613b1c@news.totallyobjects.com...
> 3) If it wasn't an issue in practice, we wouldn't need the MI rules.
That's backwards. It's -because- of the rules that it's not an issue in
practice, like a lot of things in programming, e.g. operator precedence.
| |
| Chris Uppal 2004-03-27, 12:28 am |
| Mark Wilden wrote:
> Indeed, the whole "ambiguity" issue is a red herring. In C++, for example,
> there are strict rules concerning dominance.
Most MI could be said to be "weakly ambiguous" in the sense that the ambiguity
is only resolved by memorised, and ultimately fairly arbitrary, rules. Whereas
I don't think mixins -- if the term isn't being abused -- are even weakly
ambiguous.
> Understanding these rules
> isn't harder than anything else in C++. :)
Writing C++ is endlessly fascinating: so much you have to think about to get
anything done. Ultimately a waste of brain-power; but then, so are crosswords
and people do them for *fun*...
-- chris
| |
| Lex Spoon 2004-03-27, 12:28 am |
| "David Simmons" <david.simmons@smallscript.com> writes:
> I think that the complexity/problems associated with MI are largely baggage
> from static and statically typed languages. For almost all intents and
> purposes Smalltalk is pure-encapsulation through messaging language.
Thanks for sharing your design info, David. I don't see these
differences you suggest, however, between static and dynamic
languages.
The biggest issues with MI exist just as much in Smalltalk as in C++:
1. What do you do when multiple superclasses define a variable
with the same name? How many instance variables do you make,
and how do you bind variable references to variables?
2. What do you do when multiple superclasses define the same
method? With single inheritance, you let the last one win.
What about in multiple inheritance where you have a conflict
from two ancestors that do not inherit from each other? You can
pick one side to win over the other, but what if they are internal
methods? Does one side call the method inherited from the other
side? If so, you can easily break your little mixin classes.
If not, you end up with much more complicated method-binding
semantics than are normally in Smalltalk.
3. The kicker: what if you inherit from a class *twice* ?! If you
inherit from PeristableObject, then you probably just want *one*
copy of all the ivars. If you inherit from List, then you want
*two* copies, and you want to be able to refer to each copy of
the superclass independently.
Further, one of the big differences that does exist is an argument
*against* MI in Smalltalk. Namely, a common reason to want MI is to
satisfy the type system in certain circumstances. In C++, if you have
a variable of type "dictionary", and you want to store a "foo" into
it, then "foo" has to inherit from "dictionary".
So, I would say David that you have done a lot of good thinking about
how to do MI well. But the issues you are facing seem to be equal to
those in any other OO language, statically typed or not. The next
C++'s and Java's and all should be watching what you are up to just
like the Smalltalk guys should.
-Lex
| |
| Lex Spoon 2004-03-27, 12:28 am |
| "David Simmons" <david.simmons@smallscript.com> writes:
> Binding order is based on mixin declaration order [which in a dynamic system
> maps down to the order in which a mixin was added to a given behavior].
> However, if a supertype appears after a subtype then the ordering will be
> computed to ensure that the subtype appears ahead of the subtype.
>
> Exact ordering can always we interrogated reflectively by asking a class for
> its supertypes.
All right, but that rule seems problematic, because you can "override"
methods in a right-hand class with methods in a left-hand class. You
can even un-override methods that your class has overwritten but which
are re-inherited from the parent class on the left side.
Suppose I have PersistentDog which has an inheritance chain -- I mean,
inheritance graph -- like this:
Object
/ \
PO Dog
\ /
PersistentDog
Suppose the following methods are floating around:
Object>>name
^'(unnamed)'
Dog>>name
^name
Dog>>bark
Transcript show: (self name, ' barks'); cr
Now suppose I do:
(PersistentDog named: 'Fido') bark
The transcript will show "(unnamed) barks".
In this simple case, you can fix the problem by reversing the order
that PersistentDog inherits from its superclasses. However, you can
only do that if there *is* a linear ordering that will work for your
conflicts. Further, this approach is going to be rough on the
programmer in any case; spotting that the #name override here is an
issue is not easy.
This example seems to mean that you never want a mixin to override
methods from Object. But, that's a large limitation. Further, since
this will cause a problem consistently, perhaps it should be outright
outlawed the way Traits does, instead of being left as a mine for
programmers to step on all the time?
>
> In S# model, as mentioned in another post, only <Interfaces> can have
> concrete fields. There is never more than one instance of an interface
> associated with a given object that has declared that interface as a mixin
> somewhere in it supertype hierarchy.
>
> I.e., there is always only one shared copy -- this is the internal (COM
> style) aggregation model.
Okay, but notice that some people would like two copies, or you are
missing out on some of the common examples of using mixins. For
example, you might want a LinkedList mixin that lets you make a list
of objects, and then want two different superclasses to each have one.
The fact that it quietly makes just one copy can lead to some really
"interesting" bugs. Suppose you make an AishB class which inherits
from A and B, and A and B both inherit from LinkedList for their own
internal purposes. Suddenly these classes will see objects
unexpectedly disappearing from their internal lists! Now does it fall
to the guy who writes AishB to figure out that these classes are
inheriting in a dangerous way and thus use a different (non-MI)
approach to defining AishB? (Or, does the guy have the unpleasant
experience of fixing A or B not to use LinkedList?)
This last item gets at a style issue that pushes me away from MI even
aside from the technical issues. Inheritance in general, in my view,
breaks encapsulation. It's mostly mythological that you can inherit
from a stranger's class and just tweak this or that -- the stranger
has to do a lot of work to suppor this. Assuming that extra work has
not been done, then you need to really understand any classes you
inherit a class from. Thus, inheriting from multiple classes makes
this all the worse. I don't *want* to have elaborate class
hierarchies. The poor guy who implements classes at the bottom of the
hierarchy--ahem, the dag--is going to have a tough time.
-Lex
| |
| David Simmons 2004-03-27, 12:28 am |
| "Lex Spoon" <lex@cc.gatech.edu> wrote in message
news:m3hdwe8ar4.fsf@logrus.dnsalias.net...
> "David Simmons" <david.simmons@smallscript.com> writes:
>
baggage[color=darkred]
>
> Thanks for sharing your design info, David. I don't see these
> differences you suggest, however, between static and dynamic
> languages.
The prinicipal difference I was trying to make is that in a static language
there are significant MI issues relating to the way an object's fields are
physically ordered within the object. The principal reason this is a problem
is that these languages typically allow the developer to "bypass
encapsulation" of a given classes fields by using some mechanism of direct
access.
Code that significantly exhibits this kind of capability runs the same
dangers of "sphagetti style" data access as did code of previous software
generations that exhibited sphaghetti goto/function calling. Testing becomes
problematic, and refactoring "tools" are required to re-organize the access
(if they are available, and if you have access to all the source that makes
such direct accesses). S# provides a single declarative control point to the
owner of a field which can (as a dynamic jitted language) retro-actively
change access control rules.
--
In a typical dynamic language with fields that only allow message based
access, the developer is afforded a single control point for validating
operations on a field (if necessary). That would include the ability to
rename/reroute where values get placed. This active control point ability is
missing from most static languages.
Smalltalk ensures that [other than for pool/shared-namespace fields] only
the owner (class and subclasses) of a field can directly read/write to the
field in a "sphaghetti" style. I.., from multiple access points -- without a
single programatic read/write bottleneck.
S# takes this one step further and allows any field (including shared/pool
fields) to be marked as a "property", which will then result in the
compiler/jitter ensuring that all (even by the class and its subclasses)
references to the field are done through a getter/setter accessor. Because
it is a dynamic language, the mechanism for accessing a field can be changed
at any time without needing to rewrite any code that accesses the given
field.
--
What makes a typical static language model additionally problematic is that
all the field access is resolved at compile-time and not at load or runtime.
In languages such as C++, it is worse still because you can "cast" pointers
to go even further with bypassing encapsulation control-points. And it is
the latter when combined with MI that really hurts you as a developer.
The mere fact that one has to allow such capability is what really makes
things hard in the language implementation. If it were an algorithmic access
then implementation issues in casting would go away, and thereby many
developer complexity issues [conceptual issues, testing issues, and runtime
issues] would also go away.
As a minor aside: I personally think C++ is a great (hi-performance/hi-level
OO assembly) language for a completely closed and controlled system and a
terrible language for large scale team based development of compositional
systems, and even worse for componentized systems developed by multiple
parties [I believe that lesson was learned and the case proved by the
efforts to produce the Taligent OS that plagued Apple].
As to performance concerns, a sophisticated jit design can eliminate many of
the actual algorithmic accesses and replace them with direct read/write
operations.
>
> The biggest issues with MI exist just as much in Smalltalk as in C++:
>
> 1. What do you do when multiple superclasses define a variable
> with the same name? How many instance variables do you make,
> and how do you bind variable references to variables?
S# doesn't allow multiple superclasses. It only allows one superclass, but
multiple supertypes. This provides multiple inheritance of behavior --
which, for most intents and purposes, in a pure messaging language, is MI.
The S# model synthesizes the behavior of multiple inheritance of "fields"
via an aggregate model using <Interfaces> that are aggregated onto the
core-personality object. But only the interface itself can actually
read/write its fields directly, all other parties must request access via a
message to the interface. Since, again, S# is a pure OO messaging model the
developer still writes their code exactly the same way whether the field is
directly accessed or indirectly accessed via a message.
A class/metaclass is a kind of <Constructor>. Constructors define the layout
of the objects they create, and only methods held by a constructor can
directly access the fields on the objects whose layout they define. A
supertype can provide behavior/methods to an object. Only those methods
provided by the objects constructor can directly read/write a field without
issuing a read/write message.
>
> 2. What do you do when multiple superclasses define the same
> method? With single inheritance, you let the last one win.
> What about in multiple inheritance where you have a conflict
> from two ancestors that do not inherit from each other? You can
> pick one side to win over the other, but what if they are internal
> methods? Does one side call the method inherited from the other
> side? If so, you can easily break your little mixin classes.
> If not, you end up with much more complicated method-binding
> semantics than are normally in Smalltalk.
This is a problem even in single inheritance scenarios. Especially in
Smalltalk where you can have "patches" or "packages" loaded from multiple
parties each of which "extends/overrides" the same methods in a given class.
S# provides selector namespaces (including modularized scopes) to allow
developers to declare methods as being only accessible from a given
context-group. The result is that (access privilideges) where the call is
coming from play a role in determining (dynamically binding) which given
method implementation (in the receiver target) is invoked.
The rule here is that things I explicitly define as binding to my code space
take precedence over things that an external party might define. This allows
very fine grain control of the rules for what will get bound, but ensures
that in doing so I have a controlled "locality of reference" pattern. I.e.,
all the special rules live in the same place as (or conceptually near) the
clients of those rules.
>
> 3. The kicker: what if you inherit from a class *twice* ?! If you
> inherit from PeristableObject, then you probably just want *one*
> copy of all the ivars. If you inherit from List, then you want
> *two* copies, and you want to be able to refer to each copy of
> the superclass independently.
In S#, as mentioned before, inheritance of structure is through aggregation
and there is only once instance of given structure (Interface or
core-personality) associate with a given object. So you never inherit
anything twice. You may, however, inherit its behavior at a different point
in the lattice than you declared it if some other party also made a
declaration that required it at a "closer" binding level than your
declaration.
Such situations can lead to ordering issues within your code unless you have
made use of selector namespace facilities to avoid them. Such situations can
be detected at compile, load, or runtime and analyzed through reflection
(which is something that static languages typically do not offer).
>
>
> Further, one of the big differences that does exist is an argument
> *against* MI in Smalltalk. Namely, a common reason to want MI is to
> satisfy the type system in certain circumstances. In C++, if you have
> a variable of type "dictionary", and you want to store a "foo" into
> it, then "foo" has to inherit from "dictionary".
My reasons for putting MI in S# are clearly not to satisfy a type system.
They are to facilitate compositional weaving of software in form that
minimizes complexity by providing single design and runtime control points;
thereby maximizing re-use and enabling more control over fragility issues.
FYI: I am reminded of another early S# example of using mixins, which was to
automagically wrapper collection protocols to make them thread safe when
needed, without having to pay the penalties when it is not needed. I.e.,
creating a mixin containing "around" wrapper methods and then declaring a
subclass of the appropriate collection type and simply adding in the
mixins -- no writing of methods required.
>
>
> So, I would say David that you have done a lot of good thinking about
> how to do MI well. But the issues you are facing seem to be equal to
> those in any other OO language, statically typed or not. The next
> C++'s and Java's and all should be watching what you are up to just
> like the Smalltalk guys should.
Thanks Lex. You're right, there are certainly challenges -- and there is no
substitute for good practices and good tools.
-- Dave S. [www.smallscript.org]
>
>
>
> -Lex
| |
| L. M. Rappaport 2004-03-27, 12:28 am |
| On Tue, 23 Mar 2004 13:30:22 -0000, "Chris Uppal"
<chris.uppal@metagnostic.REMOVE-THIS.org> wrote (with possible
editing):
>L. M. Rappaport wrote:
>
>
>I don't see how it would help ? Given that there is some behavior, B, that I
>want to have shared by instances of classes A and B, where would the state
>machine go, what would it do ?
>
>I'm sorry if I'm being obtuse, but I just don't understand what you mean.
>
> -- chris
>
You would use a state machine to swap behaviors. I.e., you design the
state machine class to delegate it's behaviors to the various plug-in
classes and switch them to emulate whatever you want.
I'm making the assumption here that we both use the same definition
for a state machine - an object (say O1) which encapsulates behaviors
to an object contained in an instance variable. You change the
objects to change the behaviors. O1's methods just delegate to the
methods contained in the ivar's contained object.
--
Larry
Email to rapp at lmr dot com
| |
| Steve A 2004-03-27, 12:28 am |
|
L. M. Rappaport wrote:
>...
> You would use a state machine to swap behaviors. I.e., you design the
> state machine class to delegate it's behaviors to the various plug-in
> classes and switch them to emulate whatever you want.
>
> I'm making the assumption here that we both use the same definition
> for a state machine - an object (say O1) which encapsulates behaviors
> to an object contained in an instance variable. You change the
> objects to change the behaviors. O1's methods just delegate to the
> methods contained in the ivar's contained object.
That sounds more like a strategy to me.
I was thinking that approach would be ok too. The problem is when you
want two concurrent strategies the client of the strategy must
implement at least one of the strategy protocols.
For a single strategy it is possible to use doesNotUnderstand: to make
delegation very simple. Some do not like using DNU this way as it can
introduce errors if someone extends a superclass of the strategy
client to understand part of the strategy protocol.
--
Steve Aldred
R&D Team
Application Solutions
Wizard Information Services
http://wizardis.com.au
Note the reply address is anti spam.
Use: steve aldred wizardis com au (dot at dot dot)
| |
| David Simmons 2004-03-27, 12:28 am |
| "Lex Spoon" <lex@cc.gatech.edu> wrote in message
news:m3d67289dd.fsf@logrus.dnsalias.net...
> "David Simmons" <david.simmons@smallscript.com> writes:
system[color=darkred]
be[color=darkred]
for[color=darkred]
>
> All right, but that rule seems problematic, because you can "override"
> methods in a right-hand class with methods in a left-hand class. You
> can even un-override methods that your class has overwritten but which
> are re-inherited from the parent class on the left side.
>
> Suppose I have PersistentDog which has an inheritance chain -- I mean,
> inheritance graph -- like this:
>
> Object
> / \
> PO Dog
> \ /
> PersistentDog
>
>
"" One way to declare this in S#
Mixin name: PO.
Class name: Dog
fields: auto name.
Class name: PersistentDog extends: Dog
mixins: PO.
> Suppose the following methods are floating around:
>
> Object>>name
> ^'(unnamed)'
"" Directly declare the method on <Object>
Method behavior: Object [
name
^'(unnamed)'.
]
>
> Dog>>name
> ^name
"" See above for auto getter/setter field declaration of <name>.
>
> Dog>>bark
> Transcript show: (self name, ' barks'); cr
"" Create a behavior wrapper so we can declare multiple
"" methods on <Dog> using the wrapper as the "behavior:".
Class ref.name: Dog {
Method [
bark
Transcript show: (self name, ' barks'); cr.
]
}
>
>
> Now suppose I do:
> (PersistentDog named: 'Fido') bark
>
> The transcript will show "(unnamed) barks".
That will not occur in the example, as described above and implemented in
the corresponding S# examples code.
"(self name, ' barks')" will probably complain (in classic Smalltalk) that
<name> (which is <nil> ) does not respond to the #, message selector.
I.e., The expr "PersistentDog.supertypes" returns:
{DefaultProject.PersistentDog, DefaultProject.PO, DefaultProject.Dog,
PureMixin, Mixin, Aspect, Object, nil}
From which we can see (via the behavior/method inheritance) that #name
method implementation will be picked up from <Dog> well before it is picked
up from <Object>.
The expr "PersistentDog.superclasses" returns:
{DefaultProject.Dog, Object}
Which allows us to see the field-layout (structural) inheritance.
-- sidebar on S# follows --
In the case of S#, the <nil> object implements #, to behave as an "identity"
with respect to concatentation. So "nil, expr" yields "expr" when expr is of
type <Character|String>.
I.e.,
Class ref.name: UndefinedObject {
Method [
<::+(<> )>
, <String|Character|nil> items
^items.
]
Method [
<::+=(<> )>
, item
^item.
]
}
-- end sidebar --
>
> In this simple case, you can fix the problem by reversing the order
> that PersistentDog inherits from its superclasses. However, you can
> only do that if there *is* a linear ordering that will work for your
> conflicts. Further, this approach is going to be rough on the
> programmer in any case; spotting that the #name override here is an
> issue is not easy.
S# does not allow a "diamond lattice" in the form you describe. By
definition the declaration order precludes the possibility -- there is
always an implied linearization based strictly on declaration order.
However, it is certainly still possible to create a lattice that is ordered
differently that the explicit declaration.
------------
For example:
------------
Mixin name: PO.
Mixin name: PersistentAnimal extends: PO.
Class name: Dog
mixins: PersistentAnimal
fields: auto name.
Class name: PersistentDog extends: Dog
mixins: PO.
EntryPoint [
main
stdout << PersistentDog.supertypes; cr.
]
Yields:
{DefaultProject.PersistentDog, DefaultProject.PersistentAnimal,
DefaultProject.PO, DefaultProject.Dog, PureMixin, Mixin, Aspect, Object,
nil}
And "Dog.supertypes" yields:
{DefaultProject.Dog, DefaultProject.PersistentAnimal, DefaultProject.PO,
PureMixin, Mixin, Aspect, Object, nil}
So we can see that when <PersistentDog> mixed in <PO>, it actually was
already a consumer of <PersistentAnimal> from <Dog>. Therefore, referencing
<PO> forced a subsitution/migration of <PersistantAnimal> to take <PO>'s
place. Linearization and inheritance rules require that superclasses cannot
appear before subclasses in the linearization chain.
>
> This example seems to mean that you never want a mixin to override
> methods from Object. But, that's a large limitation. Further, since
> this will cause a problem consistently, perhaps it should be outright
> outlawed the way Traits does, instead of being left as a mine for
> programmers to step on all the time?
I don't agree with the Traits solution/policies. For S#'s design such
conflicts do not occur. There are, however, a number of similar issues which
a designer may need to apply some standard patterns to (i.e., be sensitive
to).
S# offers very fine control over method ordering resolution. However, the S#
method ordering control attributes are not needed unless you are
intentionally trying to create a meta-protocol ordering pattern.
They include:
a) around/before/inner/after methods.
b) not-inheritable
c) interface-only
d) fallback-method (or specification)
e) argtype-dominant
f) rebind-method
g) dynamic scope binding (selector namespace proximity)
h) arg-type binding (multi-methods)
The two items that apply principally to Mixin scenarios like you're
referring to are (c) and (d).
Where (d) specifies that the method should only bind if no (non-fallback)
i | | |