> 1. What does Teacher.super( null, salary ) do when it > reaches the first line, super( name ), in this > constructor. Remember name is excluded in AssistantTeacher > but not in Teacher. How can the compiler generate code for > Teacher that can be used by AssistantTeacher?
It can, if it does not actually exclude fields, but just hides them. But this is stupid, inefficient etc
So, compiler cannot exclude the "name" field in any reasonable way. BUT it CAN exclude whole implementation of Teacher (fields and functions). Then the code should go:
The compiler could also exclude all Teacher fields only, if functions in Teacher do not access fields, and proper virtual accessors are provided. This is traits-alike, and it can easily be added to multiple inheritance with one additional keyword.
> 2. How did the writer of AssistantTeacher know that the > field to exclude was called name? The field is private in > Person!
Excellent point. He can't and he shouldn't. That's why whole implementation should be discarded (or at least fields).
> Traits side step these issues by not allowing fields, e.g. > assuming that a Java interface could have a definition and > hence would be a Trait then:
Well, this example is a good demonstration that multiple inheritance can do all that traits can. Just don't put fields in abstract classes.
> You could code this way using multiple inheritance and > templates (to give access to the multiple supers) in C++, > but the trait concept formalizes and enforces this style > and prevents other styles that have problems.
Well, all that traits enforce is a no-fields rule. You can do the same thing with multiple inheritance if a language enforces that abstract classes can not have fields. Or you can add a keyword "trait" that specifies classes that cannot have fields (this sounds like a good and simple solution). I can agree that no-fields is a good thing and should be added to multiple inheritance. But I cannot agree that nominal subtyping should be replaced with structural.
Also, a language that allows only traits without "field inheritance" (I just invented this term, I hope you understand it's meaning) may be too restricting. Just consider following (C++ code):
class CallbackTimer
{
... //implementation, including fields, goes here
... // this class typically needs 2-3 fields
public:
void SetTime(int time) {...};
void StartTimer() {...};
virtual Callback() =0; // "=0" means same as abstract
};
CallbackTimer is like ordinary timer, but SetTime() is used to specify certain time, and when that time expires Callback() is called.
Now imagine how many different classes are usually derived from CallbackTimer. And with traits, they should all define fields and accessors. What a waste of development time, and copy-paste work. So I wonder... how do you solve this with traits?
> You will also note that I am not saying that structural > subtyping is a good thing. I like you, don't think it is > superior to named subtyping (Christopher calls this normal > subtyping).
I think structural subtyping is inferior to named/normal/nominal subtyping. Traits may be a good idea in the sense of adding no-fields rule to multiple inheritance.
> Well, this example is a good demonstration that multiple > inheritance can do all that traits can. Just don't put > fields in abstract classes.
The other things trait do are:
1. Allow access to any of the supers, in my example calls like Student.super.toString() access toString in the super trait Student. Some multiple inheritance schemes don't allow access to all supers. For example mixins only give access to the last super mixed in.
2. Traits also force an error if it is ambiguous as to which method to call. Again some multiple dispatch schemes don't they have a rule about how to resolve the ambiguity, e.g. mixin gives priority to the last class mixed in.
3. If you exclude the whole of the class in a multiple dispatch system then you loose the methods and the fields. You really only want to resolve conflicts not exclude stuff that you want. This brings me to your the next point.
> Also, a language that allows only traits without "field > inheritance" (I just invented this term, I hope you > understand it's meaning) may be too restricting. Just > consider following (C++ code): >
> class CallbackTimer
> {
> ... //implementation, including fields, goes here
> ... // this class typically needs 2-3 fields
> public:
> void SetTime(int time) {...};
> void StartTimer() {...};
> virtual Callback() =0; // "=0" means same as
> e as abstract
> };
>
> CallbackTimer is like ordinary timer, but SetTime() is > used to specify certain time, and when that time expires > Callback() is called. > > Now imagine how many different classes are usually derived > from CallbackTimer. And with traits, they should all > define fields and accessors. What a waste of development > time, and copy-paste work. So I wonder... how do you solve > this with traits?
Most languages that use Traits, e.g. the original Smalltalk implementation and Scala, retain single inheritance as well as traits. The single inheritance would transfer the fields in your example. In my example Student and Teacher singly inherit from Person and AssistantTeacher singly inherits from Student. So for example AssistantTeacher picks up fields and methods from both Person and Student.
So what you are saying is correct. You need single inheritance and traits.
Ok now I basically agree with you, with some subtle differences. But what happened is that we are discussing wrong things. Because my basic complaint against traits was the use of structural subtyping ("requires" keyword). If traits were to use nominal subtyping, then I have very little against traits. And it looks to me that it is really easy to add nominal subtyping to traits, but then they look like multiple inheritance.
Another thing is that I was ambiguous with the term "multiple inheritance", because I dont limit it to multiple inheritance as implemented in C++, but I think it should implement many other features. Especially some nice features that traits have.
The less important thing is that, when not considering nominal/structural subtyping, I still like multiple inheritance a bit more than traits because of added flexibility. I repeat, this is not very important. For example, multiple inheritance allows me to inherit implementations (including fields) from two classes, which might be handy.
What I would like most is the following (multiple inheritance with features from traits): a) Multiple inheritance, with support for renaming to access any of the supers. b) Compiler should force an error if it is ambiguous as to which method to call. c) It should be obligatory to use keyword "accessor" to mark functions that directly access fields. d) It should be possible to use "exclude_implementation" keyword in order not to inherit implementation. In this case inheritance inherits interface only, and also specifies subtype relation. To be more precise, implementations of virtual (non-final) functions and of "accessor" functions are not inherited, but final functions are inherited. e) It should be possible to use "exclude_fields" keyword in order not to inherit fields. In this case only fields and "accessor" functions are not inherited.
> 3. If you exclude the whole of the class in a multiple > dispatch system then you loose the methods and the fields. > You really only want to resolve conflicts not exclude > stuff that you want. This brings me to your the next > point.
Well, it would be nice, but it is impossible to exclude only some features. However, do not underestimate the flexibility of "exclude_implementation" and "exclude_fields". This constructs are more powerful that what traits provide, with traits you can not even have fields, and here you are even getting the option to exclude them or not.
But, I think nobody else but me will ever like my proposal with "exclude_implementation" and "exclude_fields". If that is the case consider the option of removing c), d) and e) and instead introducing the "trait" keyword for classes that do not have fields. This would be a nice mix of multiple inheritance and traits. It's like traits with nominal subtyping.
I just thought of a perfect example, and I thought it would be nice to write about it here. I hope nobody minds this monologue I am having.
template <type T:Value>
class Queue
{
private:
.//implementation
public:
Push(T element) { }
Pop():T { }
IsEmpty():bool { }
};
template <type T:Value>
class StackImpl and interface Stack
{
private:
.//implementation
public:
Push(T element) { }
Pop():T { }
IsEmpty():bool { }
};
template <type T:Value>
class DequeueImpl and interface Dequeue :
implements Queue<T> exclude_implementation,
implements Stack<T>
{
manual_rename{
link Queue<T>:Pop to PopFront;
link Queue<T>:Push to PushBack;
link Queue<T>:IsEmpty to IsEmpty;
link Stack<T>:Pop to PopFront;
link Stack<T>:Push to PushFront;
link Stack<T>:IsEmpty to IsEmpty;
}
private:
.//implementation
public:
PushFront(T element) { }
PushBack (T element) { }
PopFront():T { }
PopBack ():T { }
IsEmpty():bool { }
};
Exlanation: - you can notice bounded generic T:Value . I need to do this to specify that T has default constructor and assignment defined, so Stack and Queue are containers of values instead of references. - what I want to do with Queue is to specify both interface and implementation at the same place, so that there is no unnecessary duplication of code. If I need only the interface of Queue, I can get it with exclude_implementation keyword. But compiler usually knows whether implementation or interface only is required. - class Stack provides both interface and implementation of stack, but here I want to give different names to implementation "StackImpl" and interface "Stack". -the keyword manual_raname in Deque means that I don't want compiler to introduce names from Queue and Stack into Dequeue. But, Deque is still a subtype, and methods from Queue and Stack are present in appropriate namespaces.
What this example demonstrates is how exclude_implementation could be used to avoid unnecessary duplication of code, and how renaming is used to solve conflicting names.
exclude_fields could solve conflicting fields in a similar manner. I would also like to point out that I know the way how to inherit the implementation of Queue into Deuque (since thier implementations are usually similar) to avoid even more unnesecary coding. But explanation is too long. - There is only a single tiny problem with this example: I could make the stack at the front or at end of deque. To solve this, again, requires too long discussion.
Any comments would be appreciated, especially criticism.
I agree with you that features like "exclude_implementation" and "exclude_fields" would be nice. One way to implement them might be for the compiler to transform the code. E.g.:
class Person {
privatefinal String name;
accessor Person( final String name ) { this.name = name; }
accessor String getName() { return name; }
accessor String toString() { return "Person: " + name; }
}
Would be replaced by the compiler with something like:
abstractclass Person$Trait {
// accessors for name
// effectively private because names have $ in them
publicabstract String get$name$accessor();
publicabstract String set$name$accessor( final String name );
String getName() { return get$name$accessor(); }
String toString() { return "Person: " + get$name$accessor(); }
}
class Person extends Person$Trait {
private String Person$name; // delete final
// accessors for name
// effectively private because names have $ in them
public String get$name$accessor() { return Person$name; }
public String set$name$accessor( final String name ) {
Person$name = name;
return name;
}
Person( final String name ) { set$name$accessor( name ); }
}
IE the compiler generates the equivalent of the Trait code. When something inherits from Person, but with the "exclude_fields" clause, it instead inherits from Person$Trait and the compiler adds the appropriate name field and accessors.
One way to implement "exclude" is code transformation. But there are some details in your last example that I dont like.
First thing that I noticed is that you mixed up my "exclude" proposal and "properties" (auto-generation of getters and setters). I like properties, but not the way they are in your example. And I dont think that compiler should automatically generate getters and setters just for the sake of "exclude".
What happened in your example is that programmer of class Person made a mistake by making toString() method an accessor. This forces compiler to exclude implementation of toString(), which is not trait alike.
Also, since I like to be picky, I want to say that it is redundant to mark constructors as accessor, since constructor which isn't is meaningless. And, I always hated C++ and Java's syntax for constructor.
And I dont think that "accessor" is a good name because it would be mixed up with accessor/mutator terminology, so some other word would be required.
abstractclass Person$Trait {
String Person$getName();
String Person$toString () { return "Person: " + Person$getName(); }
}
class Person extends Person$Trait {
private String name;
Person( String name ) { this.name = name; }
String Person$getName() { return name; }
//and, since no manual_rename is specified,
//the following two methods must be added also.
//The best way to add them would be to modify vtable
//but here I will make it simpler
String getName() { return Person$getName() }
String toString() { return Person$toString() }
}
This transformation is required for exclude_fields keyword to work. For exclude_implementation keyword to work, another class Person$Interface would have to be added as superclass of Person$Trait.
And also one important thing that I didn't mention in Deque example is that exclude_implementation allows you to have 3 completely different implementations for Stack, Queue and Deque, which is not possible with traits. It is usually the case that you really need 3 different implementations in such a hierarchy, but Deque example is specific because implementation of Deque can be used as implementation for Queue and Stack without overhead. Meaning that Deque example can be rewritten to use traits (except for the renaming part).
I think something like you propose is a good idea. It is less typing than a Trait since the Trait is automatically generated by the compiler. Perhaps there is room for an innovative language feature in a new language :)
Flat View: This topic has 21 replies
on 2 pages
[
«
|
12
]