About pushBack:

Andreas Raab andreas.raab at gmx.de
Sun Aug 5 23:32:37 UTC 2007


Jason Johnson wrote:
> I don't personally see traits as something that comes up that often.  
> 99% of the time inheritance, or some OO pattern gives the best 
> solution.  But in that 1% something like a trait is the correct solution 
> and not having it shows up a design wart.

I completely agree with this statement. And in return I *urge* you to 
look at actual code and stand corrected ;-) Let's take an example:

Here is what the base class hierarchy in Nile looks like:

AbstractCollectionStream
   ReadableCollectionStream
     ByteReader
     StringReader
   WritableCollectionStream
     ByteWriter
     StringWriter
     ReadWriteCollectionStream

Except from the -somewhat arbitrary- distinction between byte and string 
reader it's pretty standard and looks (if you replace 
AbstractCollectionStream with PositionableStream) fairly similar to what 
we have today:

PositionableStream
   ReadStream
   WriteStream
     ReadWriteStream

But now, let's look at how it works. Here is how the Nile class library 
looks when we add the traits (written as <trait>):

AbstractCollectionStream
   ReadableCollectionStream <ReadableCollectionStream - position, 
setPosition, collection, setCollection:>
     ByteReader <ByteReading - next, nextInto:, next:>
     StringReader <CharacterReading>
   WritableCollectionStream <NSTWriteableCollectionStream - position, 
setPosition:, collection, setCollection:>
     ByteWriter <ByteWriting - nextPut:, nextPutAll:>
     StringWriter <CharacterWriting - nextPut:>
     ReadWriteCollectionStream <ReadableCollectionStream - size, 
collection, setCollection:, position, setPosition:>

and the traits hierarchy itself is here:

<ByteReading>
<ByteWriting>
<CharacterReading>
<CharacterWriting>
<CollectionStream>
	<PositionableStream>
<GettableStream>
	<Stream>
<PositionableStream>
	<Stream>
<PuttableStream>
	<Stream>
<ReadableCollectionStream>
	<GettableStream @ basicBack -> back>
	<CollectionStream>
<Stream>
<WriteableCollectionStream>
	<PuttableStream>
	<CollectionStream>

Now let's start counting: Instead of a single inheritance hierarchy with 
8 entities (classes) that I need to know about I have 19 entities (8 
classes, 11 traits) in an MI hierarchy. In addition we have nested 
traits (so you'll have to know the other inheritance branches fairly 
well), renames (ReadableCollectionStream) and exclusions (6 out of 8 
classes have exclusions) complicating the structure even further. A more 
realistic complexity measure (counting the number of occurrences of the 
various entities) ends up with about 4x the complexity of the traits 
version (which is certainly what it feels like).

So why would this be advantageous to a single inheritance hierarchy?

I'm pretty sure someone will come back and say "but wait! isn't the idea 
that *other* clients will use those traits and in return will be simpler 
and better and easier?" So let's look at this too:

I'll be looking at the Files hierarchy in Nile for the sole purpose that 
it is the only comparable piece that has counter parts in Squeak today 
and Squeak's files are certainly messy enough that they need a good 
makeover. So here is the hierarchy for files in Nile (again without the 
traits):

AbstractFileStream
   AbstractBinaryStream
     ReadFileBinaryStream
     WriteFileBinaryStream
     ReadWriteFileBinaryStream
   AbstractTextStream
     ReadFileTextStream
     WriteFileTextStream
     ReadWriteFileTextStream

The structure is ... shall we say unusual and (I think) heavily 
influenced by the desire to create permutations of existing traits 
instead of applying other OO design principles. And here is the version 
with traits information included:

AbstractFileStream
   AbstractBinaryStream
     ReadFileBinaryStream <ReadFileStream - fileName, bufferType, 
getFileID, setFileID: setFileName> <ByteReading>
     WriteFileBinaryStream <WriteFileStream - fileName, bufferType, 
getFileID, setFileID: setFileName> <ByteWriting>
     ReadWriteFileBinaryStream <ReadFileStream - fileName, bufferType, 
getFileID, setFileID: setFileName> <WriteFileStream - fileName, 
bufferType, getFileID, setFileID: setFileName> <ByteWriting>
   AbstractTextStream
     ReadFileTextStream <ReadFileStream - fuleName, bufferType, 
getFileID, setFileID: setFileName> <CharacterReading>
     WriteFileTextStream <WriteFileStream - fileName, bufferType, 
getFileID, setFileID: setFileName> <CharacterWriting>
     ReadWriteFileTextStream <ReadFileStream - fileName, bufferType, 
getFileID, setFileID: setFileName> <WriteFileStream - fileName, 
bufferType, getFileID, setFileID: setFileName> <ByteWriting>

<FileStream>
   <PositionableStream - isBinary>
<ReadFileStream>
   <GettableStream - close, isBinary, isClosed>
   <FileStream>
<WriteFileStream>
   <PuttableStream - close, isBinary, isClosed>
   <FileStream>

The amount of added complexity is mindboggling to me, and other than 
AbstractFileStream being a subclass of Object there doesn't seem to be 
*any* advantage of using traits. The disadvantages however, like the 
explosion in the number of entities are very real (I think anyone who 
has tried to understand the Squeak 3.9 and later meta class hierarchy 
will agree).

So ultimately, I agree with your statement. There may be *some* uses for 
traits, but those are few and far inbetween (and, realistically 
speaking, nobody has found one yet). In the cases that we have seen so 
far, traits are simply used as MI with all the same problems that MI 
has. Nile certainly shows the symptoms (added complexity, conflicts) 
that we've come to recognize as the hallmark of MI systems.

Cheers,
   - Andreas




More information about the Squeak-dev mailing list