Traits is major feature added since 3.9. And i think it should STAY. I don't see any good reason, why next releases should not support traits.
Instead of thinking about, how to get rid of them, i think we should think about, what in traits implementation prevents people from loading code based on 3.8 without much stress, and based on this knowledge, refactor traits to be more compatible with other non-traitified code.
But they should stay!
Hi Igor
Thanks. Apparently I got 300 emails blocked somewhere. I thought I was not in the mailing-list anymore. So I will summarize all the emails in this one:
About traits: - I would love to see if we can get a simpler, loadable on demand BUT ROBUST implementation of traits. I will have a look at Andreas code. Thanks for that. - I'm not sure that sharing method is a good idea. - I'm not sure that the tradeoff introduce traits is worth but if do not try it how can we learn. for traits in the kernel: there is duplicated code in the kernel, so it was natural to try. We designed especially traits to be backward compatible so that we can learn and still can backtrack if not needed/bad/idiot/stupid. - It seems to me that the Smalltalk spirit of inventing the future is lost in Squeak. May be should wait for Newspeak and leave there... - We will work on adding state to traits (not following the design of stateful traits because too complex for nothing). I like a lot when people think that we are "researchers" you know the guys that don't give a shit and do not know what is real development with balls. Seriously there are a **lot** of cool experiences we did (changesbox, classboxes....) and we never even think about to put them in a language. Why we did it for traits, because it was simple, backwards compatible, and good. Now this is clear that it introduces noise and you have to choose between traits and subclass and you have to fix the tools. I wish we would be with a language that does not have an IDE. But we are serious about our meta-concerns, we are eating our own dog food. - I'm convinced after the experience with nathanael on collection and damien on stream that traits are good. - We will run a new large analysis and build a new collection hierarchy (if I get funding) from september. - I find a bit strange that people gets so annoyed by traits when they can ignore them. - looks at lukas with Seaside: it does not use them and do not see them. - now if the people really against traits would make real arguments like (the reflective interface is not good enough when I use selector I get all the selector and this is not clear how to get the local one versus the traits one.... and you can find other examples) - Instead of bashing traits, try to use them. The OB version of david rotlisberger is supporting traits. - There are a lot of work to do to get a better Squeak (see my previous email when I gave such a list to Edgar).
Stef
On May 15, 2008, at 6:33 PM, Igor Stasenko wrote:
Traits is major feature added since 3.9. And i think it should STAY. I don't see any good reason, why next releases should not support traits.
Instead of thinking about, how to get rid of them, i think we should think about, what in traits implementation prevents people from loading code based on 3.8 without much stress, and based on this knowledge, refactor traits to be more compatible with other non-traitified code.
But they should stay!
-- Best regards, Igor Stasenko AKA sig.
I am genuinely curious to see a simple example that clearly demonstrates the advantages of traits.
It is my perception (or misperception) that traits are an attempt (perhaps unknowingly) to bring multiple inheritance into the language. As I see it, this confuses Y Is-A X (i.e. Y subclasses X) with Y Has-A-Part X (i.e. Y has a member variable / subpart / trait X)
I've seen this in GUI toolkits - e.g. someone has a Bitmap class and a ClickableArea class. They want to make a Button - i.e. something that has both Bitmap and ClickableArea traits. So they create Button by inheriting from both Bitmap and ClickableArea classes (usually C++). The better solution is to say that Button has both Bitmap and ClickableArea parts (i.e. member variables). This confusion becomes ridiculously clear if we attempt to create e.g. a Human class by inheriting from Eye, Leg, Arm etc. There is very likely much confusion between all of the members that get dragged in and try to compete with each other.
So, if I have a mistaken view of traits, I would in all honesty love to see an example that shows this to me clearly.
I have a strong view that software should be designed with a very high view of orthogonality. I will often reject non-orthogonal approaches in my own software without much thought, simply because I have been down that road before and seen the trouble that ensues. That's why I would suggest that traits should be rejected IFF they turn out to be a non-orthogonal design (i.e. their utility is duplicated by e.g. member variables). I am not yet convinced one way or the other.
please read our papers!
Stef
On May 16, 2008, at 10:52 AM, Ryan Mitchley wrote:
I am genuinely curious to see a simple example that clearly demonstrates the advantages of traits.
It is my perception (or misperception) that traits are an attempt (perhaps unknowingly) to bring multiple inheritance into the language. As I see it, this confuses Y Is-A X (i.e. Y subclasses X) with Y Has-A-Part X (i.e. Y has a member variable / subpart / trait X)
I've seen this in GUI toolkits - e.g. someone has a Bitmap class and a ClickableArea class. They want to make a Button - i.e. something that has both Bitmap and ClickableArea traits. So they create Button by inheriting from both Bitmap and ClickableArea classes (usually C++). The better solution is to say that Button has both Bitmap and ClickableArea parts (i.e. member variables). This confusion becomes ridiculously clear if we attempt to create e.g. a Human class by inheriting from Eye, Leg, Arm etc. There is very likely much confusion between all of the members that get dragged in and try to compete with each other.
So, if I have a mistaken view of traits, I would in all honesty love to see an example that shows this to me clearly.
I have a strong view that software should be designed with a very high view of orthogonality. I will often reject non-orthogonal approaches in my own software without much thought, simply because I have been down that road before and seen the trouble that ensues. That's why I would suggest that traits should be rejected IFF they turn out to be a non-orthogonal design (i.e. their utility is duplicated by e.g. member variables). I am not yet convinced one way or the other.
-- View this message in context: http://www.nabble.com/-squeak-dev--My-view-on-Traits-tp17257352p17270291.htm... Sent from the Squeak - Dev mailing list archive at Nabble.com.
stephane ducasse wrote:
please read our papers!
Stef
Hi Stef
Could you please post a link to your papers, and possibly point me to a page that has a simple example.
Thanks.
Ryan
On Fri, May 16, 2008 at 11:51 AM, Ryan Mitchley ryan.mitchley@gmail.com wrote:
Could you please post a link to your papers, and possibly point me to a page that has a simple example.
For a simple example, you can read the last paper which contains an appendix with a small presentation of traits (page 32 and nexts): http://www.iam.unibe.ch/~scg/Archive/Papers/Cass08a-NileNewKernel-ComputerLa...
The complete trait bibliography is at: http://www.iam.unibe.ch/~scg/cgi-bin/scgbib.cgi?query=traits
I advise you to read Traits: «Composable Units of Behavior» http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf
If you like to have an example look at the collections hierarchy. That is a perfect example for that. These are full of "diamond problems". And what options do you have to solve it? Here my top approaches (worst first):
- code duplication. Don't need to comment that - delegation. Adds a lot of one line methods. Code is growing and is error prone - moving implementation up the hierarchy. This leads to something like ArrayedCollection>>add: newObject self shouldNotImplement - traits. Not easy to understand and adds complexity
I'm not sure which one is better of the last two. And I'm also not sure if collections are the only useful example for using traits. For me it is an language option I can use or not. Every approach has its drawbacks you have to think about. For me even inheritance can be seen that way.
This discussion raises some tensions. For me it is important to make a difference between "my opinion about traits" and "if traits should stay in the image". And I hope we don't discuss about banning traits from the image. To make traits a real option it should be removed but only if it is reloadable. Removing it without being able to reload it would be a big loss.
So, where's the problem? Even Stef agrees that to have it reloadable would be a good choice. I think it's because it has to be proven and be done.
Norbert
On Fri, 2008-05-16 at 01:52 -0700, Ryan Mitchley wrote:
I am genuinely curious to see a simple example that clearly demonstrates the advantages of traits.
It is my perception (or misperception) that traits are an attempt (perhaps unknowingly) to bring multiple inheritance into the language. As I see it, this confuses Y Is-A X (i.e. Y subclasses X) with Y Has-A-Part X (i.e. Y has a member variable / subpart / trait X)
I've seen this in GUI toolkits - e.g. someone has a Bitmap class and a ClickableArea class. They want to make a Button - i.e. something that has both Bitmap and ClickableArea traits. So they create Button by inheriting from both Bitmap and ClickableArea classes (usually C++). The better solution is to say that Button has both Bitmap and ClickableArea parts (i.e. member variables). This confusion becomes ridiculously clear if we attempt to create e.g. a Human class by inheriting from Eye, Leg, Arm etc. There is very likely much confusion between all of the members that get dragged in and try to compete with each other.
So, if I have a mistaken view of traits, I would in all honesty love to see an example that shows this to me clearly.
I have a strong view that software should be designed with a very high view of orthogonality. I will often reject non-orthogonal approaches in my own software without much thought, simply because I have been down that road before and seen the trouble that ensues. That's why I would suggest that traits should be rejected IFF they turn out to be a non-orthogonal design (i.e. their utility is duplicated by e.g. member variables). I am not yet convinced one way or the other.
On Fri, May 16, 2008 at 12:00 PM, Norbert Hartl norbert@hartl.name wrote:
If you like to have an example look at the collections hierarchy. That is a perfect example for that. These are full of "diamond problems". And what options do you have to solve it? Here my top approaches (worst first):
- code duplication. Don't need to comment that
- delegation. Adds a lot of one line methods. Code is growing and is
error prone
- moving implementation up the hierarchy. This leads to something like
ArrayedCollection>>add: newObject self shouldNotImplement
- traits. Not easy to understand and adds complexity
For a discussion about these questions and the Stream hierarchy, have a look at «Traits at Work: the design of a new trait-based stream library» http://www.iam.unibe.ch/~scg/Archive/Papers/Cass08a-NileNewKernel-ComputerLa.... Here, we identify all problems with the current Stream hierarchy, design a new one based on traits and discuss the differences and drawbacks.
Damien Cassou wrote:
For a discussion about these questions and the Stream hierarchy, have a look at «Traits at Work: the design of a new trait-based stream library» http://www.iam.unibe.ch/~scg/Archive/Papers/Cass08a-NileNewKernel-ComputerLa.... Here, we identify all problems with the current Stream hierarchy, design a new one based on traits and discuss the differences and drawbacks.
This is a very nice paper but it falls into the same trap that other traits papers fall into - it confuses ideas that are just as applicable to single inheritance with traits and claims that (just because traits have been used to implement the original idea) traits are superior.
For example: In section 7.3 some metrics are given that compare the Squeak implementation with Nile's implementation of internal streams. Based on the (superior) version in Nile it says that "This means we avoided reimplementation of a lot of methods by putting them in the right traits. Finally, we can deduce from the last metrics that the design of Nile is better: there is neither cancelled methods nor methods implemented too high and there are only three methods reimplemented for speed reasons compared to the fourteen of the Squeak version." with the strong implication that it was traits that helped to achieve the majority of the improvements.
But is that so? Let's look at section 4.1 and note that here it is pointed out that "The library implements all collection-oriented methods in a single class: CollectionStream. This class replaces the three classes ReadStream, ReadWriteStream and WriteStream of the traditional Smalltalk stream hierarchy (See Figure 1)." This approach is definitely an improvement but every bit as applicable to a single inheritance implementation of streams.
It is interesting to do a quick check to see how much this might change matters: First, combining these three classes into one means that the traits version has now twice the number of entities vs. the non-traits version (3 vs 6). This view is also supported by counting the "backward compatible" part of Figure 12 (which is directly comparable with the Squeak version) which results in 11 classes and traits (compared to 5 classes in Squeak).
Next, if we take the total number of methods in these three classes:
ReadStream selectors size + WriteStream selectors size + ReadWriteStream selectors size ----------------- 68
(the measure was taken in 3.9 to be roughly comparable with the paper and I'm not sure why the paper claims 55 methods) and compare this with the number of unique selectors (discounting all re-implemented methods):
(Set withAll: (ReadStream selectors asArray), (WriteStream selectors asArray), (ReadWriteStream selectors asArray)) size ------------------- 59
What we get is 15% improvement *minimum* by folding these three classes (very likely more if one looks in detail).
Next, let's look at "canceled methods" (those that use #shouldNotImplement). The paper lists 2 canceled methods which happen to be WriteStream>>next and ReadStream>>nextPut:. And of course those wouldn't exist in a single-inheritance implementation either. Etc.
In other words, the measures change *dramatically* as soon as we apply the original idea regardless of whether traits are used or not. Which speaks clearly for the original idea of folding these three classes into one but concluding that traits have anything to do with it would require a very different comparison.
If the paper wants to make any claims regarding traits, it really needs to distinguish improvements that are due to traits from general improvements (i.e., improvements that are just as applicable to single-inheritance implementations). Otherwise it is comparing apples to oranges and can't be taken seriously in this regard.
Cheers, - Andreas
Yes. As far as using streams as an example, I never understood why support for write-only streams was ever needed. What's wrong with just assuming all streams are readable? Then this classic dilemma just goes away. It seems to me that whoever wrote the first internal streams implementation for Smalltalk simply got that part wrong, and no one questioned it for a long time. I wouldn't make this a primary motivating example for traits (hopefully there's a better one).
-C
On Fri, 16 May 2008 17:01:08 -0700, Craig Latta craig@netjam.org wrote:
Yes. As far as using streams as an example, I never understood why
support for write-only streams was ever needed. What's wrong with just assuming all streams are readable?
Not all streams =are= readable. I'm not sure that's a serious problem, but it is a reality.
2008/5/17 Craig Latta craig@netjam.org:
Yes. As far as using streams as an example, I never understood why
support for write-only streams was ever needed. What's wrong with just assuming all streams are readable? Then this classic dilemma just goes away. It seems to me that whoever wrote the first internal streams implementation for Smalltalk simply got that part wrong, and no one questioned it for a long time. I wouldn't make this a primary motivating example for traits (hopefully there's a better one).
There are lot of hardware having a write-only capabilities, yes they can be bidirectional and software built on top of them can imitate read/write behavior. A devices like com port or network card are bidirectional devices which support both reading and writing but this not means that you can read back what you just written to it, or do seek.
One example from my real experience. I was involved in a project to develop VoIp software (like Skype). I was hired just after point, when main design decisions was made. A principal design failure was, that communication between parties was built on a bidirectional stream principles (say two hosts connecting and starting conversation by exchanging voice data streams). And when we came to point where we started implementing a 'conference calls' (when more than two people involved in conversation), it was become clear to all (not only me, who warned about that ;) ), that it is better to represent a conversation as a set of independent listeners and independent media sources. And you may see an analogy to read/write only streams: listener is read only stream, while media source is write-only stream which should stay decoupled from each other in example above.
-C
...I never understood why support for write-only streams was ever needed. What's wrong with just assuming all streams are readable? Then this classic dilemma just goes away. It seems to me that whoever wrote the first internal streams implementation for Smalltalk simply got that part wrong, and no one questioned it for a long time. I wouldn't make this a primary motivating example for traits (hopefully there's a better one).
There are lot of hardware having a write-only capabilities...
I was speaking of internal streams, but even in the external stream case, you can just leave it to the external resource to complain if an inappropriate reading operation is attempted (not to the stream itself).
-C
-- Craig Latta improvisational musical informaticist www.netjam.org Smalltalkers do: [:it | All with: Class, (And love: it)]
On 16-May-08, at 5:01 PM, Craig Latta wrote:
Yes. As far as using streams as an example, I never understood
why support for write-only streams was ever needed. What's wrong with just assuming all streams are readable? Then this classic dilemma just goes away. It seems to me that whoever wrote the first internal streams implementation for Smalltalk simply got that part wrong, and no one questioned it for a long time. I wouldn't make this a primary motivating example for traits (hopefully there's a better one).
Heh - I've long had nearly the opposite question: Why was support for read-write streams ever needed? I can't recall *ever* using it in my own code, nor encountering it in anyone else's.
The one exception, of course, is SocketStream, and I think it proves the rule. I've had to dig into that code to implement a missing feature, and let me tell you... it's complicated. I'd much rather have seen clients use two separate streams, or even wrap them in a DuplexStream if having a single object is that important.
Colin
On Sat, May 17, 2008 at 9:48 AM, Colin Putney cputney@wiresong.ca wrote:
Heh - I've long had nearly the opposite question: Why was support for read-write streams ever needed? I can't recall *ever* using it in my own code, nor encountering it in anyone else's.
What about files?
Avi
On 17-May-08, at 1:36 PM, Avi Bryant wrote:
On Sat, May 17, 2008 at 9:48 AM, Colin Putney cputney@wiresong.ca wrote:
Heh - I've long had nearly the opposite question: Why was support for read-write streams ever needed? I can't recall *ever* using it in my own code, nor encountering it in anyone else's.
What about files?
Good point. A lot of files are read-only or write-only, but not always. MC2's file repository both reads and writes on the same file stream. So I guess I have encountered it in someone else's code. :-)
Still, I do think separate streams for reading and writing to the same file would be fine as an API.
Colin
2008/5/18 Colin Putney cputney@wiresong.ca:
On 17-May-08, at 1:36 PM, Avi Bryant wrote:
On Sat, May 17, 2008 at 9:48 AM, Colin Putney cputney@wiresong.ca wrote:
Heh - I've long had nearly the opposite question: Why was support for read-write streams ever needed? I can't recall *ever* using it in my own code, nor encountering it in anyone else's.
What about files?
Good point. A lot of files are read-only or write-only, but not always. MC2's file repository both reads and writes on the same file stream. So I guess I have encountered it in someone else's code. :-) Still, I do think separate streams for reading and writing to the same file would be fine as an API. Colin
Want to second that. In most cases read and write operations (even operating with same device) serving different purposes, and really can be used apart without much increase in code complexity.
Unless you using another trait - a positionable stream, the write and read streams can be easily decoupled.
Hi,
On Fri, May 16, 2008 at 4:38 PM, Andreas Raab andreas.raab@gmx.de wrote: ...
It is interesting to do a quick check to see how much this might change matters: First, combining these three classes into one means that the traits version has now twice the number of entities vs. the non-traits version (3 vs 6). This view is also supported by counting the "backward compatible"
Less entities are not necessarily better than more, as I´m sure you know. Generally, more classes with a clear responsibility are better than less, harder to understand, classes.
part of Figure 12 (which is directly comparable with the Squeak version) which results in 11 classes and traits (compared to 5 classes in Squeak).
Next, if we take the total number of methods in these three classes:
ReadStream selectors size + WriteStream selectors size + ReadWriteStream selectors size
68
(the measure was taken in 3.9 to be roughly comparable with the paper and I'm not sure why the paper claims 55 methods) and compare this with the number of unique selectors (discounting all re-implemented methods):
(Set withAll: (ReadStream selectors asArray), (WriteStream selectors asArray), (ReadWriteStream selectors asArray)) size
59
What we get is 15% improvement *minimum* by folding these three classes (very likely more if one looks in detail).
Next, let's look at "canceled methods" (those that use #shouldNotImplement). The paper lists 2 canceled methods which happen to be WriteStream>>next and ReadStream>>nextPut:. And of course those wouldn't exist in a single-inheritance implementation either. Etc.
In other words, the measures change *dramatically* as soon as we apply the original idea regardless of whether traits are used or not. Which speaks clearly for the original idea of folding these three classes into one but concluding that traits have anything to do with it would require a very different comparison.
If the paper wants to make any claims regarding traits, it really needs to distinguish improvements that are due to traits from general improvements (i.e., improvements that are just as applicable to single-inheritance implementations). Otherwise it is comparing apples to oranges and can't be taken seriously in this regard.
But there *are* limits to what you can achieve with single inheritance. It is not very hard to come up with an example:
The Magnitude class is the perfect candidate for being converted into a trait, if you ask me. Here is its class comment:
I'm the abstract class Magnitude that provides common protocol for objects that have the ability to be compared along a linear dimension, such as dates or times. Subclasses of Magnitude include Date, ArithmeticValue, and Time, as well as Character and LookupKey.
My subclasses should implement < aMagnitude = aMagnitude hash
Subclasses of Magnitude, by implementing #< #= #hash, gain methods #<= #> #>= #between:and: #hashMappedBy: #max: #min: #min:max:. The subclasses of Magnitude are Number, Character, DateAndTime, etc.
String does not subclass Magnitude, it subclasses ArrayedCollection, and yet it does implement #< #= and #hash. It could clearly benefit from using Magnitude as a trait (indeed, it does implement #hashMappedBy: exactly as Magnitude).
Having traits like Magnitude leave you more options to define a better inheritance hierarchy.
Saludos,
Víctor Rodríguez.
Cheers,
- Andreas
Victor Rodriguez wrote:
If the paper wants to make any claims regarding traits, it really needs to distinguish improvements that are due to traits from general improvements (i.e., improvements that are just as applicable to single-inheritance implementations). Otherwise it is comparing apples to oranges and can't be taken seriously in this regard.
But there *are* limits to what you can achieve with single inheritance. It is not very hard to come up with an example:
I'm not saying that there aren't any limits to single inheritance. I'm saying the paper is comparing apples and oranges and it shouldn't do that but rather be clear what improvements are due to the use of MI and which aren't.
Cheers, - Andreas
Thanks victor. This is the point.
I stopped to comment these kind of "counting less classes = better" argument. And stop to consider all the feedback useful: we changed nile because the first design was not good. If people like the Stream hierarchy, they can just keep it. I think that we are focusing on the wrong problem and at the end frankly I believe that we did our job and pretty well. We will continue and we want to build collection hierarchy based on traits, but don't worry this will not be in Squeak.
Stef
On May 17, 2008, at 3:28 AM, Victor Rodriguez wrote:
Hi,
On Fri, May 16, 2008 at 4:38 PM, Andreas Raab andreas.raab@gmx.de wrote: ...
It is interesting to do a quick check to see how much this might change matters: First, combining these three classes into one means that the traits version has now twice the number of entities vs. the non-traits version (3 vs 6). This view is also supported by counting the "backward compatible"
Less entities are not necessarily better than more, as I´m sure you know. Generally, more classes with a clear responsibility are better than less, harder to understand, classes.
part of Figure 12 (which is directly comparable with the Squeak version) which results in 11 classes and traits (compared to 5 classes in Squeak).
Next, if we take the total number of methods in these three classes:
ReadStream selectors size + WriteStream selectors size + ReadWriteStream selectors size
68
(the measure was taken in 3.9 to be roughly comparable with the paper and I'm not sure why the paper claims 55 methods) and compare this with the number of unique selectors (discounting all re-implemented methods):
(Set withAll: (ReadStream selectors asArray), (WriteStream selectors asArray), (ReadWriteStream selectors asArray)) size
59
What we get is 15% improvement *minimum* by folding these three classes (very likely more if one looks in detail).
Next, let's look at "canceled methods" (those that use #shouldNotImplement). The paper lists 2 canceled methods which happen to be WriteStream>>next and ReadStream>>nextPut:. And of course those wouldn't exist in a single-inheritance implementation either. Etc.
In other words, the measures change *dramatically* as soon as we apply the original idea regardless of whether traits are used or not. Which speaks clearly for the original idea of folding these three classes into one but concluding that traits have anything to do with it would require a very different comparison.
If the paper wants to make any claims regarding traits, it really needs to distinguish improvements that are due to traits from general improvements (i.e., improvements that are just as applicable to single-inheritance implementations). Otherwise it is comparing apples to oranges and can't be taken seriously in this regard.
But there *are* limits to what you can achieve with single inheritance. It is not very hard to come up with an example:
The Magnitude class is the perfect candidate for being converted into a trait, if you ask me. Here is its class comment:
I'm the abstract class Magnitude that provides common protocol for objects that have the ability to be compared along a linear dimension, such as dates or times. Subclasses of Magnitude include Date, ArithmeticValue, and Time, as well as Character and LookupKey.
My subclasses should implement < aMagnitude = aMagnitude hash
Subclasses of Magnitude, by implementing #< #= #hash, gain methods #<= #> #>= #between:and: #hashMappedBy: #max: #min: #min:max:. The subclasses of Magnitude are Number, Character, DateAndTime, etc.
String does not subclass Magnitude, it subclasses ArrayedCollection, and yet it does implement #< #= and #hash. It could clearly benefit from using Magnitude as a trait (indeed, it does implement #hashMappedBy: exactly as Magnitude).
Having traits like Magnitude leave you more options to define a better inheritance hierarchy.
Saludos,
Víctor Rodríguez.
Cheers,
- Andreas
stephane ducasse wrote:
Thanks victor. This is the point.
I stopped to comment these kind of "counting less classes = better" argument.
The paper itself seems to have a different view on this. It states on p.22 "The first metric indicates that Nile has *only* one more entity (class/trait) than the Squeak implementation." (emphasis mine) So obviously the author(s) of the paper seem to feel that having "only" one more entity is something quite desirable. Which is why I was pointing out that this measure is incorrect and the correct statement should be "Nile has twice as many entities when compared to a corresponding Squeak implementation" regardless of its interpretation.
And stop to consider all the feedback useful: we changed nile because the first design was not good. If people like the Stream hierarchy, they can just keep it.
I like Nile. I think it contains some good ideas. But I don't think that much of that is due to the use of traits and I won't let people get away with making claims (in a journal paper no less) that have no basis in reality. In particular when it comes to metrics - we can disagree on whether the structure is more or less easily understood when using traits but I don't think we should disagree on how to count the number of classes and traits in Nile or a corresponding Squeak implementation.
I think that we are focusing on the wrong problem and at the end frankly I believe that we did our job and pretty well. We will continue and we want to build collection hierarchy based on traits, but don't worry this will not be in Squeak.
Sure. Whatever. If you want to evade the discussions by running away, be my guest. Wrong data is wrong data though and if you keep comparing apples and oranges other people will ask the same questions.
Cheers, - Andreas
On Sat, May 17, 2008 at 2:02 PM, Andreas Raab andreas.raab@gmx.de wrote:
Wrong data is wrong data though and if you keep comparing apples and oranges other people will ask the same questions.
We based our comparison on something that existed for a long time. Moreover, ANSI defines a set of protocols that we wanted to reflect in our implementation. Now, you propose something new and probably interesting in absence of traits. If I understand correctly, your proposal does not follow ANSI. This is not a problem, but we tried to follow it when we designed Nile. I'm afraid that comparing Nile with your design would lead to compare apples and bananas :-). But still, I'm very interested in your proposal. It would be cool if you could provide diagrams and/or implementation.
Hi Andreas,
thank you for your comment. I'm not sure what is exactly the design you propose. However, it seems you won't be able to cleanly implement FileStream, History and Decoder, do you? Could you please provide a set of diagrams so that we can compare apples and apples :-)?
Have a nice week-end
Damien Cassou wrote:
thank you for your comment. I'm not sure what is exactly the design you propose. However, it seems you won't be able to cleanly implement FileStream, History and Decoder, do you? Could you please provide a set of diagrams so that we can compare apples and apples :-)?
I'll think about it. The main problem with the paper is that it takes the triad ReadStream, WriteStream, ReadWriteStream and replaces those with CollectionStream and then lists a bunch of numbers that I simply cannot recreate. For example, how did you come up with the "number of methods" in Table 6? This doesn't compute for me at all - neither if I look at only those three classes, nor when I look at the number of inherited stream methods.
Also, how do you decide whether "methods are implemented too high" in the hierarchy? For table 6, which methods did you consider to be implemented too high and are these counted in the total number of methods (which I doubt since the total number of methods is too small to include methods in superclasses so by definition it shouldn't count those).
About FileStream, History and Decoder: Please provide a definition of "cleanly implement" so that I can see if a solution to the problem exists. FileStream and Decoder look fairly straightforward though so unless you have a very restricted definition of "clean" I'm pretty sure I can come up with something.
Cheers, - Andreas
On Sat, May 17, 2008 at 2:27 PM, Andreas Raab andreas.raab@gmx.de wrote:
I'll think about it. The main problem with the paper is that it takes the triad ReadStream, WriteStream, ReadWriteStream and replaces those with CollectionStream and then lists a bunch of numbers that I simply cannot recreate. For example, how did you come up with the "number of methods" in Table 6? This doesn't compute for me at all - neither if I look at only those three classes, nor when I look at the number of inherited stream methods.
Please have a look at the NSMetrics class. Here, you have the methods which generate the tables and their numbers. If you find any problem with the algorithms, please tell me.
Also, how do you decide whether "methods are implemented too high" in the hierarchy?
The algorithm is in NSMetrics too.
For table 6, which methods did you consider to be implemented too high and are these counted in the total number of methods (which I doubt since the total number of methods is too small to include methods in superclasses so by definition it shouldn't count those).
About FileStream, History and Decoder: Please provide a definition of "cleanly implement" so that I can see if a solution to the problem exists. FileStream and Decoder look fairly straightforward though so unless you have a very restricted definition of "clean" I'm pretty sure I can come up with something.
I have no definition of "cleanly implemented". I guess I would consider your approach clean if you avoid methods implemented too high and duplicated code. It is also important that you try to provide the same features as Squeak and Nile do. The last thing I'm thinking about right now is interface pollution: please avoid adding writing behavior (like #nextPut) to read-only streams like Random and Decoder. The same goes for positioning (with position:) and History.
Damien Cassou wrote:
Please have a look at the NSMetrics class. Here, you have the methods which generate the tables and their numbers. If you find any problem with the algorithms, please tell me.
Excellent, thanks. I had never seen NSMetrics before. Is there a particular version of Nile that should be used for those measures? I tried 0.9.5 (the latest published universe version available in 3.10) and it blows up, first because there is no trait NSTCollectionStream and later because it sends #overridesSelectors which isn't implemented anywhere.
Cheers, - Andreas
On Sat, May 17, 2008 at 3:40 PM, Andreas Raab andreas.raab@gmx.de wrote:
Damien Cassou wrote:
Please have a look at the NSMetrics class. Here, you have the methods which generate the tables and their numbers. If you find any problem with the algorithms, please tell me.
Excellent, thanks. I had never seen NSMetrics before. Is there a particular version of Nile that should be used for those measures? I tried 0.9.5 (the latest published universe version available in 3.10) and it blows up, first because there is no trait NSTCollectionStream and later because it sends #overridesSelectors which isn't implemented anywhere.
I'm sorry. I haven't updated the universe package for some time. Please use SqueakSource and load Nile-All. This package depends on other required package and contains NSMetrics.
Damien Cassou wrote:
I'm sorry. I haven't updated the universe package for some time. Please use SqueakSource and load Nile-All. This package depends on other required package and contains NSMetrics.
Okay, after loading this I understand better where the numbers come from. First, a couple of comments on NSMetrics: #methodsInClassAndMetaclass:methodListBlock: does a union of methods in class and metaclass which looks a little questionable to me. I don't think it matters here but it seems odd to count a method in class and metaclass only once. The #numberOfReimplementedMethodsForClasses: also has two problems in such that it does only look at methods overridden in the direct superclass (so it doesn't find methods implemented in Stream and overridden in ReadStream but not in PositionableStream) and that it excludes the required selectors of traits but not those of superclasses (i.e., self subclassResponsibility) which it should discount as well (see note below on the metrics that are affected by it).
That said, we can now devise a comparison which is more appropriate for a Nile vs. Squeak comparison. I've attached a simple class InternalStream which as a subclass of PositionableStream implements the same folding of ReadStream, WriteStream, and ReadWriteStream. I believe it to be a fully functioning equivalent to NSCollectionStream. If we run the design metrics using InternalStream instead of the three other classes we end up with metrics that look like this (slightly reformatted from the TeX output):
Squeak Nile (Squeak-Nile)/Squeak Number of Classes And Traits 3 6 -100% Number of Classes 3 1 66% Number of Methods 39 33 15% [*1] Number of Bytes 1328 1078 18% Number of Cancelled Methods 0 0 0% Number of Reimplemented Methods 10 3 70% [*1] Number of Methods Impl. Too High 0 0 0%
[*1] This includes 2 subclassResponsibilities in Squeak which should be discounted as pointed out above.
The main differences are in the number of entities as well as in the number of methods (overrides). Looking at it in detail it turns out that the larger number of entities comes purely from the more fine-grained structure of traits (only one class but five traits) and the larger number of methods come from overrides where InternalStream has either more efficient versions (#upTo: #next: #upToEnd) or needs to compensate PositionableStream assuming that the position will be within its readLimit (#position: #setToEnd #reset) or implements required subclassResponsibilities (#atEnd, #contents).
It is interesting to see that the traits version can do without most of those overrides although it isn't clear to me that this would remain a lasting advantage. One could rewrite the Squeak collection hierarchy to do without these overrides by relaxing the constraints on PositionableStream and use more effective versions by default. This would improve these metrics but I'm not sure it is in the spirit of the Squeak collection hierarchy.
That said, I would also slightly refactor NSCollectionStream into, e.g.,
NSPositionableStream <NSTGettableStream + NSPuttableStream + NSPositionableStream> NSCollectionStream
The idea in the above refactoring is to keep the "composition class" (NSPositionableStream) separate from the "implementation class" (NSCollectionStream). It really makes it easier to see what you've done in NSCollectionStream and having a class used only to gather the traits also makes it more clear that anything you'd implement at that level really belongs into a trait and not into the class. It makes looking at classes with traits almost bearable ;-)
Cheers, - Andreas
Thank you for your analyses. I'll have a close look as soon as possible and will let you know.
On Sat, May 17, 2008 at 10:32 PM, Andreas Raab andreas.raab@gmx.de wrote:
Damien Cassou wrote:
I'm sorry. I haven't updated the universe package for some time. Please use SqueakSource and load Nile-All. This package depends on other required package and contains NSMetrics.
Okay, after loading this I understand better where the numbers come from. First, a couple of comments on NSMetrics: #methodsInClassAndMetaclass:methodListBlock: does a union of methods in class and metaclass which looks a little questionable to me. I don't think it matters here but it seems odd to count a method in class and metaclass only once. The #numberOfReimplementedMethodsForClasses: also has two problems in such that it does only look at methods overridden in the direct superclass (so it doesn't find methods implemented in Stream and overridden in ReadStream but not in PositionableStream) and that it excludes the required selectors of traits but not those of superclasses (i.e., self subclassResponsibility) which it should discount as well (see note below on the metrics that are affected by it).
That said, we can now devise a comparison which is more appropriate for a Nile vs. Squeak comparison. I've attached a simple class InternalStream which as a subclass of PositionableStream implements the same folding of ReadStream, WriteStream, and ReadWriteStream. I believe it to be a fully functioning equivalent to NSCollectionStream. If we run the design metrics using InternalStream instead of the three other classes we end up with metrics that look like this (slightly reformatted from the TeX output):
Squeak Nile (Squeak-Nile)/Squeak
Number of Classes And Traits 3 6 -100% Number of Classes 3 1 66% Number of Methods 39 33 15% [*1] Number of Bytes 1328 1078 18% Number of Cancelled Methods 0 0 0% Number of Reimplemented Methods 10 3 70% [*1] Number of Methods Impl. Too High 0 0 0%
[*1] This includes 2 subclassResponsibilities in Squeak which should be discounted as pointed out above.
The main differences are in the number of entities as well as in the number of methods (overrides). Looking at it in detail it turns out that the larger number of entities comes purely from the more fine-grained structure of traits (only one class but five traits) and the larger number of methods come from overrides where InternalStream has either more efficient versions (#upTo: #next: #upToEnd) or needs to compensate PositionableStream assuming that the position will be within its readLimit (#position: #setToEnd #reset) or implements required subclassResponsibilities (#atEnd, #contents).
It is interesting to see that the traits version can do without most of those overrides although it isn't clear to me that this would remain a lasting advantage. One could rewrite the Squeak collection hierarchy to do without these overrides by relaxing the constraints on PositionableStream and use more effective versions by default. This would improve these metrics but I'm not sure it is in the spirit of the Squeak collection hierarchy.
That said, I would also slightly refactor NSCollectionStream into, e.g.,
NSPositionableStream <NSTGettableStream + NSPuttableStream + NSPositionableStream> NSCollectionStream
The idea in the above refactoring is to keep the "composition class" (NSPositionableStream) separate from the "implementation class" (NSCollectionStream). It really makes it easier to see what you've done in NSCollectionStream and having a class used only to gather the traits also makes it more clear that anything you'd implement at that level really belongs into a trait and not into the class. It makes looking at classes with traits almost bearable ;-)
Cheers,
- Andreas
Hi Andreas,
On Sat, 2008-05-17 at 13:32 -0700, Andreas Raab wrote:
Damien Cassou wrote:
I'm sorry. I haven't updated the universe package for some time. Please use SqueakSource and load Nile-All. This package depends on other required package and contains NSMetrics.
I've updated the Universe.
Okay, after loading this I understand better where the numbers come from. First, a couple of comments on NSMetrics: #methodsInClassAndMetaclass:methodListBlock: does a union of methods in class and metaclass which looks a little questionable to me. I don't think it matters here but it seems odd to count a method in class and metaclass only once.
I haven't find a place where it was a problem. If you do, please tell me.
The #numberOfReimplementedMethodsForClasses: also has two problems in such that it does only look at methods overridden in the direct superclass (so it doesn't find methods implemented in Stream and overridden in ReadStream but not in PositionableStream) and that it excludes the required selectors of traits but not those of superclasses (i.e., self subclassResponsibility) which it should discount as well (see note below on the metrics that are affected by it).
I need to have a deeper look into this. Thanks for pointing me. If you already have a fix, could you please sent it?
That said, we can now devise a comparison which is more appropriate for a Nile vs. Squeak comparison. I've attached a simple class InternalStream which as a subclass of PositionableStream implements the same folding of ReadStream, WriteStream, and ReadWriteStream. I believe it to be a fully functioning equivalent to NSCollectionStream. If we run the design metrics using InternalStream instead of the three other classes we end up with metrics that look like this (slightly reformatted from the TeX output):
Squeak Nile (Squeak-Nile)/Squeak
Number of Classes And Traits 3 6 -100% Number of Classes 3 1 66% Number of Methods 39 33 15% [*1] Number of Bytes 1328 1078 18% Number of Cancelled Methods 0 0 0% Number of Reimplemented Methods 10 3 70% [*1] Number of Methods Impl. Too High 0 0 0%
[*1] This includes 2 subclassResponsibilities in Squeak which should be discounted as pointed out above. The main differences are in the number of entities as well as in the number of methods (overrides). Looking at it in detail it turns out that the larger number of entities comes purely from the more fine-grained structure of traits (only one class but five traits) and the larger number of methods come from overrides where InternalStream has either more efficient versions (#upTo: #next: #upToEnd) or needs to compensate PositionableStream assuming that the position will be within its readLimit (#position: #setToEnd #reset) or implements required subclassResponsibilities (#atEnd, #contents).
In the paper, I compared the existing design of Squeak and Nile. In my opinion it was fair in this respect.
In your version, you compare something that does not exist and is *not* functionality equivalent. With your design, you can't simply implement the clients we present in the paper. You could also have reimplemented Stream and PositionableStream in InternalStream and have better metrics :-). I understand that it would be much work and you probably don't want to do it. This discussion clearly indicates that my metrics are not self-contained. It would be interesting to have another line to show the "reusability" of both systems. However, I don't know what can be calculated to show that :-). If you have an opinion, please tell me.
It is interesting to see that the traits version can do without most of those overrides although it isn't clear to me that this would remain a lasting advantage.
Why? Do you mean Nile currently misses features and adding them might break this?
One could rewrite the Squeak collection hierarchy to do without these overrides by relaxing the constraints on PositionableStream and use more effective versions by default. This would improve these metrics but I'm not sure it is in the spirit of the Squeak collection hierarchy.
The Squeak collection hierarchy needs to be changed also :-D. You probably won't agree with a new design based on traits ;-).
That said, I would also slightly refactor NSCollectionStream into, e.g.,
NSPositionableStream <NSTGettableStream + NSPuttableStream + NSPositionableStream> NSCollectionStream
I don't understand your refactoring. Where are NSTGettablePositionable and NSTPuttablePositionable? Could you please be more explicit?
The idea in the above refactoring is to keep the "composition class" (NSPositionableStream) separate from the "implementation class" (NSCollectionStream). It really makes it easier to see what you've done in NSCollectionStream and having a class used only to gather the traits also makes it more clear that anything you'd implement at that level really belongs into a trait and not into the class. It makes looking at classes with traits almost bearable ;-)
I agree and that's what I do at the beginning. However, I didn't wanted to add more entities than really necessary.
On 1-Jun-08, at 2:57 AM, Damien Cassou wrote:
I've updated the Universe.
I don't think it worked. I still see a bunch of idiots running the place, poverty, irrationality, religiosity, and the sea is just the wrong shade of pink this time. Please re-try your updating :-)
tim -- tim Rowledge; tim@rowledge.org; http://www.rowledge.org/tim Computer possessed? Try DEVICE=C:\EXOR.SYS
On Sun, Jun 1, 2008 at 6:13 PM, tim Rowledge tim@rowledge.org wrote:
On 1-Jun-08, at 2:57 AM, Damien Cassou wrote:
I've updated the Universe.
I don't think it worked. I still see a bunch of idiots running the place, poverty, irrationality, religiosity, and the sea is just the wrong shade of pink this time. Please re-try your updating :-)
I works for me. I took a 3.10.1 image, opened the Universe Browser, updated the list, went to Model Extension, selected Nile 1.0.122 and Installed selection.
On 1-Jun-08, at 11:53 PM, Damien Cassou wrote:
On Sun, Jun 1, 2008 at 6:13 PM, tim Rowledge tim@rowledge.org wrote:
On 1-Jun-08, at 2:57 AM, Damien Cassou wrote:
I've updated the Universe.
I don't think it worked. I still see a bunch of idiots running the place, poverty, irrationality, religiosity, and the sea is just the wrong shade of pink this time. Please re-try your updating :-)
I works for me. I took a 3.10.1 image, opened the Universe Browser, updated the list, went to Model Extension, selected Nile 1.0.122 and Installed selection.
I fear my lame attempt at a joke may have failed. :-(
tim -- tim Rowledge; tim@rowledge.org; http://www.rowledge.org/tim Useful Latin Phrases:- Aio, quantitas magna frumentorum est. = Yes, that is a very large amount of corn.
On 02.06.2008, at 19:31, tim Rowledge wrote:
On 1-Jun-08, at 11:53 PM, Damien Cassou wrote:
On Sun, Jun 1, 2008 at 6:13 PM, tim Rowledge tim@rowledge.org wrote:
On 1-Jun-08, at 2:57 AM, Damien Cassou wrote:
I've updated the Universe.
I don't think it worked. I still see a bunch of idiots running the place, poverty, irrationality, religiosity, and the sea is just the wrong shade of pink this time. Please re-try your updating :-)
I works for me. I took a 3.10.1 image, opened the Universe Browser, updated the list, went to Model Extension, selected Nile 1.0.122 and Installed selection.
I fear my lame attempt at a joke may have failed. :-(
We need to drop more Monty Python on the French I guess ;)
Looking at the immediately visible part of the Universe around me, it doesn't seem to have been updated much either.
- Bert -
Hi,
On Mon, Jun 2, 2008 at 8:01 PM, Bert Freudenberg bert@freudenbergs.de wrote:
We need to drop more Monty Python on the French I guess ;)
only on the French?
Looking at the immediately visible part of the Universe around me, it doesn't seem to have been updated much either.
I just got an update notification today: junior eats mortadella now. OK, it's maybe a minor update to most people, but anyway. There *are* updates. ;-)
Best,
Michael
It was pretty funny, but I wondered too if people would get it.
Here another that isn't quite as drastic as Updating the entire universe.
How does a programmer fix a car?
How you ask. Well I say,
S/he gets out gets back in and tries it again.
:)
Ron
-----Original Message----- From: tim Rowledge
On 1-Jun-08, at 11:53 PM, Damien Cassou wrote:
On Sun, Jun 1, 2008 at 6:13 PM, tim Rowledge tim@rowledge.org wrote:
On 1-Jun-08, at 2:57 AM, Damien Cassou wrote:
I've updated the Universe.
I don't think it worked. I still see a bunch of idiots running the place, poverty, irrationality, religiosity, and the sea is just the wrong shade of pink this time. Please re-try your updating :-)
I works for me. I took a 3.10.1 image, opened the Universe Browser, updated the list, went to Model Extension, selected Nile 1.0.122 and Installed selection.
I fear my lame attempt at a joke may have failed. :-(
tim
tim Rowledge; tim@rowledge.org; http://www.rowledge.org/tim Useful Latin Phrases:- Aio, quantitas magna frumentorum est. = Yes, that is a very large amount of corn.
On 2-Jun-08, at 3:58 PM, Ron Teitelbaum wrote:
How does a programmer fix a car?
How you ask. Well I say,
S/he gets out gets back in and tries it again.
No, that's only for Windows users...
tim -- tim Rowledge; tim@rowledge.org; http://www.rowledge.org/tim Useful random insult:- Has an inferiority complex, but not a very good one.
HAHAHA!
-----Original Message----- From: tim Rowledge
On 2-Jun-08, at 3:58 PM, Ron Teitelbaum wrote:
How does a programmer fix a car?
How you ask. Well I say,
S/he gets out gets back in and tries it again.
No, that's only for Windows users...
tim
tim Rowledge; tim@rowledge.org; http://www.rowledge.org/tim Useful random insult:- Has an inferiority complex, but not a very good one.
Yes please reboot... they are even destroying amazonia....
On Jun 1, 2008, at 6:13 PM, tim Rowledge wrote:
On 1-Jun-08, at 2:57 AM, Damien Cassou wrote:
I've updated the Universe.
I don't think it worked. I still see a bunch of idiots running the place, poverty, irrationality, religiosity, and the sea is just the wrong shade of pink this time. Please re-try your updating :-)
tim
tim Rowledge; tim@rowledge.org; http://www.rowledge.org/tim Computer possessed? Try DEVICE=C:\EXOR.SYS
Hi Damien -
Sorry this is going to be lengthy, but you are raising an interesting set of issues so here we go.
Damien Cassou wrote:
NSMetrics>>methodsInClassAndMetaclass:methodListBlock:
I haven't find a place where it was a problem. If you do, please tell me.
Me neither. It just struck me as a potential source of troubles.
NSMetrics>>numberOfReimplementedMethodsForClasses:
I need to have a deeper look into this. Thanks for pointing me. If you already have a fix, could you please sent it?
Unfortunately, I don't. (I looked over the relevant results manually because I was surprised about some of them and then noticed the issues)
In the paper, I compared the existing design of Squeak and Nile. In my opinion it was fair in this respect.
I don't want to split hairs so I'll be blunt. I don't think it was fair to compare the two because of the conclusions you draw from the comparison, for example:
"Nile has 40% less methods and 39% less bytecodes than the corresponding Squeak collection-based stream library. This means we avoided reimplementation of a lot of methods by putting them in the right traits."
This is at the very best misleading since it implies that the difference in those measures is due to applying traits. The reality is that you used traits *and* made other refactorings, refactorings that are every bit as applicable to a non-traits implementation. And the measurable amount of improvements that can be directly attributed to traits is precisely zero. A fair conclusion would be to say:
"Nile has 40% less methods and 39% less bytecodes than the corresponding Squeak collection-based stream library. However, this is the result of applying several refactorings and cannot be directly attributed to the use of traits."
In your version, you compare something that does not exist and is *not* functionality equivalent. With your design, you can't simply implement the clients we present in the paper.
Well, let's be careful here. Since I posted the code how can you claim it doesn't exist? InternalStream is *exactly* equivalent in functionality to NSCollectionStream so when you combine it with Stream and PositionableStream you have a hierarchy that is *exactly* equivalent in functionality to Nile's internal stream hierarchy.
As a result, I was taking the measures for exactly those parts that you chose in the paper, no more no less (there are no comparisons of other clients in the paper either - they all refer to the internal stream hierarchy and I compared the precise same set of functionality).
You could also have reimplemented Stream and PositionableStream in InternalStream and have better metrics :-).
Yes, and it would have been utterly pointless, too ;-) I didn't want such a comparison, I wanted one where the application of the same refactoring that merges Read/Write/ReadWriteStream is done with the same intent as the existing collection hierarchy.
I understand that it would be much work and you probably don't want to do it. This discussion clearly indicates that my metrics are not self-contained. It would be interesting to have another line to show the "reusability" of both systems. However, I don't know what can be calculated to show that :-). If you have an opinion, please tell me.
I don't have an exhaustive answer but a couple of measures that look both at cost and benefit might be: * "Explicit duplication" of code: How much code is explicitly duplicated in the hierarchy? * "Total reuse" of code: How much of the code is reused either by being inherited or used from a trait? * Explicit requirements: How many required methods must a subclass or trait user implement to reuse the intended behavior? (btw, your provided / required measure is the wrong way around because the "interesting" measure is how much work you need to do to reap the benefit of reusing some entity)
Those would be interesting measures because they can be applied to both single and multiple inheritance hierarchies statistically and they might put perspective on some issues. For example: How big of a problem is explicit code duplication in a single inheritance hierarchy when compared with the cost of using traits? Question like these, when taken from a real system comparison could lead to very interesting results.
It is interesting to see that the traits version can do without most of those overrides although it isn't clear to me that this would remain a lasting advantage.
Why? Do you mean Nile currently misses features and adding them might break this?
I mean that some of the basics of the Squeak stream hierarchy got put into place some twenty years ago and some assumptions simply changed over time. I would expect the same to be true for Nile, in particular if the quantitative reuse of traits is higher. For example, consider that a trait defines an operation that raises an exception (#next anyone? ;-) and that five years later you figured you were wrong and that you'd rather have it silently return nil. Can you fix that at this point? Seems *very* unlikely if those traits are widely used. So what happens is that every "new" user of that trait will start overwriting the method so you get into precisely the same situation as with the current collection hierarchy. It seems unavoidable in the long term that some of these issues will happen (and yes, "in theory" all of that is fixable but I'm talking about a practical situation).
The Squeak collection hierarchy needs to be changed also :-D. You probably won't agree with a new design based on traits ;-).
Well, I certainly *do* agree with the internal stream refactoring. And there are others where I'd violently agree that (almost) anything is better than the current state of affairs (files for example). What I disagree with is that these refactorings are best applied using traits.
Now, I'll happily admit that the design of Nile is pretty good, considering that it's using MI. However, I think that a modern single-inheritance design of streams can be just as effective without having to resort to the unavoidable complexity of MI solutions. It would certainly look different; Nile is clearly designed for MI so just duplicating it would be a useless exercise. But for example, check out flow for comparison - this is a pretty good example.
That said, I would also slightly refactor NSCollectionStream into, e.g.,
NSPositionableStream <NSTGettableStream + NSPuttableStream + NSPositionableStream> NSCollectionStream
I don't understand your refactoring. Where are NSTGettablePositionable and NSTPuttablePositionable? Could you please be more explicit?
Remove them. The idea is to provide extension points so instead of using the traits you provide (super-) classes as extension points. NSTGettablePositionable and NSTPuttablePositionable are needless entities that don't add any value and only make things harder to understand (and to draw - if you remove them from your picture the structure becomes significantly simpler). If you want an easy extension point make it a class and hide all that traits mess in it. If you want to have uni-directional streams you could add them via class extension points too (and not need NSTGettablePositionable or NSTPuttablePositionable either).
The idea in the above refactoring is to keep the "composition class" (NSPositionableStream) separate from the "implementation class" (NSCollectionStream). It really makes it easier to see what you've done in NSCollectionStream and having a class used only to gather the traits also makes it more clear that anything you'd implement at that level really belongs into a trait and not into the class. It makes looking at classes with traits almost bearable ;-)
I agree and that's what I do at the beginning. However, I didn't wanted to add more entities than really necessary.
How interesting. I didn't know that. Doesn't it strike you as at least somewhat odd that you found that structure to be useful during development and then got rid of it as "unneeded" scaffolding? If you found it useful you might consider that other people might find it useful too.
Cheers, - Andreas
Andreas Raab a écrit :
I mean that some of the basics of the Squeak stream hierarchy got put into place some twenty years ago and some assumptions simply changed over time. I would expect the same to be true for Nile, in particular if the quantitative reuse of traits is higher. For example, consider that a trait defines an operation that raises an exception (#next anyone? ;-) and that five years later you figured you were wrong and that you'd rather have it silently return nil. Can you fix that at this point? Seems *very* unlikely if those traits are widely used. So what happens is that every "new" user of that trait will start overwriting the method so you get into precisely the same situation as with the current collection hierarchy. It seems unavoidable in the long term that some of these issues will happen (and yes, "in theory" all of that is fixable but I'm talking about a practical situation).
Cheers,
- Andreas
Hello Andreas, If you insist on it, yes, I must agree that answering nil at the end of a Character Stream is a good trade-off and a simple lightweight implementation. (Due to low cost of == nil, Exception becomes faster only for large files). But, it is also a very dumb thing to do with generic Streams of Objects:
Collection allSubInstances count: [:e | [e includes: nil] ifError: [false]].
My image counts 16785 such nil holders...
Cheers
nicolas cellier wrote:
Andreas Raab a écrit :
I mean that some of the basics of the Squeak stream hierarchy got put into place some twenty years ago and some assumptions simply changed over time. I would expect the same to be true for Nile, in particular if the quantitative reuse of traits is higher. For example, consider that a trait defines an operation that raises an exception (#next anyone? ;-) and that five years later you figured you were wrong and that you'd rather have it silently return nil. Can you fix that at this point? Seems *very* unlikely if those traits are widely used. So what happens is that every "new" user of that trait will start overwriting the method so you get into precisely the same situation as with the current collection hierarchy. It seems unavoidable in the long term that some of these issues will happen (and yes, "in theory" all of that is fixable but I'm talking about a practical situation).
Hello Andreas, If you insist on it, yes, I must agree that answering nil at the end of a Character Stream is a good trade-off and a simple lightweight implementation.
I'm not insisting on *anything*. I'm making up an example where a reasonable person could come up with arguments either way and where -as time goes by- the arguments may way out differently. This had absolutely nothing to do with actual Stream>>next behavior; it had everything to do with the fact that there are good reasons for making it go either way. And no, I have absolutely no interest in discussing this issue and will stay out of any further discussion about what the "right" stream behavior is.
Cheers, - Andreas
Andreas Raab a écrit :
I'm not insisting on *anything*. I'm making up an example where a reasonable person could come up with arguments either way and where -as time goes by- the arguments may way out differently. This had absolutely nothing to do with actual Stream>>next behavior; it had everything to do with the fact that there are good reasons for making it go either way. And no, I have absolutely no interest in discussing this issue and will stay out of any further discussion about what the "right" stream behavior is.
Cheers,
- Andreas
Seems like I missed the interesting point.
What would be the single inheritance scheme? Provide some kind of pluggable behaviour with additional inst vars like an objectToReturnWhenAtEndOfStream or a blockToExecuteWhenAtEndOfStream?
Your proposing to override when the logic would be to fragment in more traits.
It seems appealing to construct a custom stream from traits composition: Readable , AnswerNilAtEndOFStream , CanStepOneObjectBack (Peekable). But I agree that the price seems high (leads to greater fragmentation of code with distributed constraints, or in other words less encapsulation and more interfaces).
Nicolas
nicolas cellier wrote:
Seems like I missed the interesting point.
And I don't think you got it now either ;-) My point was that over time fundamental assumptions can change and that for a widely reused behavior it will be very difficult to change fundamental assumptions, resulting in the necessity to use overrides to represent changed understanding. It seems unavoidable. You see this in Squeak for example in the overrides of ReadStream and PositionableStream and my conclusion was that although Nile can do without those right now I am not certain it would stay that way in the long term; in particular for situations where it is easy to argue either way.
What would be the single inheritance scheme? Provide some kind of pluggable behaviour with additional inst vars like an objectToReturnWhenAtEndOfStream or a blockToExecuteWhenAtEndOfStream?
This has nothing to do with inheritance schemes. Overrides exist in both.
Your proposing to override when the logic would be to fragment in more traits.
It seems appealing to construct a custom stream from traits composition: Readable , AnswerNilAtEndOFStream , CanStepOneObjectBack (Peekable). But I agree that the price seems high (leads to greater fragmentation of code with distributed constraints, or in other words less encapsulation and more interfaces).
I think you meant appalling not appealing ;-)
Cheers, - Andreas
Andreas Raab a écrit :
nicolas cellier wrote:
Seems like I missed the interesting point.
And I don't think you got it now either ;-) My point was that over time fundamental assumptions can change and that for a widely reused behavior it will be very difficult to change fundamental assumptions, resulting in the necessity to use overrides to represent changed understanding. It seems unavoidable. You see this in Squeak for example in the overrides of ReadStream and PositionableStream and my conclusion was that although Nile can do without those right now I am not certain it would stay that way in the long term; in particular for situations where it is easy to argue either way.
Yes, and after 2 or 3 overrides, the code rots and stinks enough that a complete refactoring is wanted (every user of Squeak should have his own example in mind)... eventually leading to these traits questions...
It's quite impossible to answer whether traits would defer the need for overriding or not on fair basis...
If implementation is more complex, it could eventually be broken faster by unaware code maintainers and rot faster...
What would be the single inheritance scheme? Provide some kind of pluggable behaviour with additional inst vars like an objectToReturnWhenAtEndOfStream or a blockToExecuteWhenAtEndOfStream?
This has nothing to do with inheritance schemes. Overrides exist in both.
For something core like the #next example, overriding is hardly an option, because you have to override several Stream branches and some #next senders too. Thats why i bounced on composition, but seems like nicolas interest does not match Andreas interest.
Your proposing to override when the logic would be to fragment in more traits.
It seems appealing to construct a custom stream from traits composition: Readable , AnswerNilAtEndOFStream , CanStepOneObjectBack (Peekable). But I agree that the price seems high (leads to greater fragmentation of code with distributed constraints, or in other words less encapsulation and more interfaces).
I think you meant appalling not appealing ;-)
Thanks for teaching me some ugly words, that was funny (and easy too).
My point was to question about multiple inheritance pros, and composition is IMO the one that could justify Nile attempt. Sure, in simple inheritance world i have other solutions like pluggable behaviour, composition by inst var, stream wrappers... I don't expect a response, I already know and respect your opinion. I feel like I'd better try by myself to forge my own.
Cheers
nicolas cellier a écrit :
My point was to question about multiple inheritance pros, and composition is IMO the one that could justify Nile attempt.
What would be traits about but composition? I mean expandable custom composition
I think that this discussion is missing an important point.
First of all both the original scheme and Nile provide the basic streams implementation. Let us say for argument sake that they are both equal in design, relative complexity and performance.
Now what benefits do I as a user have if I want to implement my own class stream like class? Does Traits offer me any advantages, and do those advantages scale to larger systems, and long term maintainability?
The number of times I look at an object with a #propertyAt: #propertyAt:put: leads me to assume that traits must be good for something.
Keith
"Ryan Mitchley" ryan.mitchley@gmail.com wrote in message
As I see it, this confuses Y Is-A X (i.e. Y subclasses X) with Y Has-A-Part X (i.e. Y has a member variable / subpart / trait X)
What about Y plays role R1, and role R2
e.g. a Car plays the role of TransportationVehicle and CollateralAsset.
The "state" related to these two roles are not independent: as you rack up miles on the transportation vehicle, the asset collateral value drops; when the asset is hauled away for non-payment, the transportation vehicle is not available.
Doing this with "has-A" becomes quite intricate, unless you systematically set up a notification channel between the parts and the whole. Perhaps traits are cleaner for these (within their limitations e.g. static, not dynamic; cannot rename callback methods, only alias).
Sophie
Sophie2 wrote:
What about Y plays role R1, and role R2
e.g. a Car plays the role of TransportationVehicle and CollateralAsset.
The "state" related to these two roles are not independent: as you rack up miles on the transportation vehicle, the asset collateral value drops; when the asset is hauled away for non-payment, the transportation vehicle is not available.
It is precisely Car's function to mediate between its parts (and provide a useful interface). The containing class provides the "glue". If the parts have good interfaces and have been well designed, the interaction will be well manageable. A fundamentally complex relationship would require fundamentally complex interactions.
The interaction between TransportationVehicle>>Miles and CollateralAsset>>Value would be radically different for, say, a Ferrari F430 and a Fiat Seicento (apologies to any offended!). It would be a function of the particular Car instance.
I think we could agree that seizure would be physically constrained to the Car! (that the seizor cared chiefly about the CollateralAsset is irrelevant...)
On Fri, 16 May 2008 10:52:57 +0200, Ryan Mitchley wrote:
I am genuinely curious to see a simple example that clearly demonstrates the advantages of traits.
It is my perception (or misperception) that traits are an attempt (perhaps unknowingly) to bring multiple inheritance into the language.
I think you can easily judge for yourself whether or not a system gives you multiple inheritance (MI):
o in Smalltalk every class has at most one superclass o every MI class has at least two superclasses o in Smalltalk a method can send to self or super o a method in an MI class can choose to which super it sends
Compare Squeak's traits to these statements ...
/Klaus
As I see it, this confuses Y Is-A X (i.e. Y subclasses X) with Y Has-A-Part X (i.e. Y has a member variable / subpart / trait X)
I've seen this in GUI toolkits - e.g. someone has a Bitmap class and a ClickableArea class. They want to make a Button - i.e. something that has both Bitmap and ClickableArea traits. So they create Button by inheriting from both Bitmap and ClickableArea classes (usually C++). The better solution is to say that Button has both Bitmap and ClickableArea parts (i.e. member variables). This confusion becomes ridiculously clear if we attempt to create e.g. a Human class by inheriting from Eye, Leg, Arm etc. There is very likely much confusion between all of the members that get dragged in and try to compete with each other.
So, if I have a mistaken view of traits, I would in all honesty love to see an example that shows this to me clearly.
I have a strong view that software should be designed with a very high view of orthogonality. I will often reject non-orthogonal approaches in my own software without much thought, simply because I have been down that road before and seen the trouble that ensues. That's why I would suggest that traits should be rejected IFF they turn out to be a non-orthogonal design (i.e. their utility is duplicated by e.g. member variables). I am not yet convinced one way or the other.
Klaus D. Witzel wrote:
I think you can easily judge for yourself whether or not a system gives you multiple inheritance (MI):
o in Smalltalk every class has at most one superclass o every MI class has at least two superclasses o in Smalltalk a method can send to self or super o a method in an MI class can choose to which super it sends
Compare Squeak's traits to these statements ...
I don't think this is a particularly good definition of MI but the above certainly applies to traits: When a class uses a trait it certainly has multiple superclasses for all its observable behavior; when the method in the trait is changed the behavior in the class changes unless it has been reimplemented locally, which is the behavior of superclasses.
For the "super sends" the only reason for aliasing in traits that I'm aware of is to get access for a trait user to a "particular version of the superclass method". Even in the earliest papers there was an example about a "colored rectangle" derived from TColor and TRectangle used aliases to get to specific implementations in its "super classes".
In other words, even though it may be called a little differently, the concepts that you describe are all present. Which is why traits are generally treated as a form of MI.
Cheers, - Andreas
On Sat, 17 May 2008 10:12:47 +0200, Andreas Raab wrote:
Klaus D. Witzel wrote:
I think you can easily judge for yourself whether or not a system gives you multiple inheritance (MI): o in Smalltalk every class has at most one superclass o every MI class has at least two superclasses o in Smalltalk a method can send to self or super o a method in an MI class can choose to which super it sends Compare Squeak's traits to these statements ...
I don't think this is a particularly good definition of MI but the above certainly applies to traits: When a class uses a trait it certainly has multiple superclasses for all its observable behavior;
Sure, one can view it like that indeed (in particular *if* Trait *would* inherit from ClassDescription ;)
when the method in the trait is changed the behavior in the class changes unless it has been reimplemented locally, which is the behavior of superclasses.
For the "super sends" the only reason for aliasing in traits that I'm aware of is to get access for a trait user to a "particular version of the superclass method". Even in the earliest papers there was an example about a "colored rectangle" derived from TColor and TRectangle used aliases to get to specific implementations in its "super classes".
Right. To be able to mimic "which super is meant" one has to carefully handcraft composition rules and invent aliases in order to arrive at a conflict-free situation (hopefully maintainable by the next hacker/user ;) And since a user (*and* its superclasses) can have own methods, conflict-free can mean cause of conflicts from both sides.
This (potential usability/mutual influence) is one of the reasons that I'm interested in Trygve's work on roles (i.e. arrive at traits which are independent of potential users).
In other words, even though it may be called a little differently, the concepts that you describe are all present. Which is why traits are generally treated as a form of MI.
Which is why it *can* be confusing ;)
Cheers,
- Andreas
squeak-dev@lists.squeakfoundation.org