The Artima Developer Community
Sponsored Link

Weblogs Forum
Implementing Implicit Behavioral Subtyping using Traits

35 replies on 3 pages. Most recent reply: Jan 23, 2006 5:30 PM by Max Lybbert

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 35 replies on 3 pages [ « | 1 2 3 | » ]
Max Lybbert

Posts: 314
Nickname: mlybbert
Registered: Apr, 2005

Re: Maybe I can answer Posted: Jan 17, 2006 6:13 AM
Reply to this message Reply
Advertisement
Oops, I still didn't answer. Let's try again:

/* ... Instead you'd create weak references, which *cannot* delete the object (compile-time error, so the programmer is still around to decide if he really wants to delete the pointer).
*/

And the answer is: in the example given, assuming y is a strong reference, and z is a weak one -- z.b() will *not* work because it will be refering to a null pointer. However, the programmer will receive a message saying so at compile time so that the error can be fixed.

As long as y is a strong reference (and it would have to be, as it is the first pointer to the object), that will be the case (even if z is a strong reference -- you'll get a "z trying to reference a deleted object" message *at* *compile* *time*).

This message should prompt the programmer to move the delete y command to after the z.b() command. However, in the example, the programmer would be better off dropping the pointers.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Implementing Implicit Behavioral Typing using Traits Posted: Jan 17, 2006 6:29 AM
Reply to this message Reply
> > The object "a" will be deleted when it goes out of
> scope.
> > Traits works more or less like references in C++.
>
> Yes, I understand that.But you didn't understand my
> question. Let me ask the question this way:
>
> Is this code
> def main() {
> CA a;
> TB b = Renamer(a);
> }
>
> equivalent to this:
> def main() {
> CA a;
> Renamer r = a;
> TB b = r;
> }

Well the code snippets would behave less the same, but you lead me to an error I made. The Renamer object was a temporary, so b would have been left referring to a temorary Renamer object which would have been deleted at the end of the statement. This would have caused an error of someone tried to use b afterwards, for example:


def main() {
CA a;
TB b = Renamer(a);
b.whatever(); // runtime error (or worse, UB)
}


So the second example you give is the correct way to do things.

> And if it is, would this work:
>
> def main() {
> CA x;
> Renamer *y = new Renamer(x);
> TB z = *y;
>
> delete y;
>
> //will this work?
> z.b();
> }

The answer is not in the current version of HeronFront, but I plan on making it possible in later versions.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Now I have a question Posted: Jan 17, 2006 6:31 AM
Reply to this message Reply
> I finally read the Traits thesis. As presented in the
> thesis, traits are stateless (although they can demand
> that the object being wrapped have get_y or set_z
> methods). Will Heron follow suit?

Yes.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Maybe I can answer Posted: Jan 17, 2006 7:20 AM
Reply to this message Reply
> Oops, I still didn't answer. Let's try again:
>
> /* ... Instead you'd create weak references, which
> *cannot* delete the object (compile-time error, so the
> programmer is still around to decide if he really wants to
> delete the pointer).
> */

Even though what you say is correct, unfortunately weak and strong pointers are not featured in the current version of Heron. They will be taking a temporary back seat to other more improtant functionality for the time being. This is part of a new focus on getting an actual compiler out before I have to start a desk job.

FYI: the next release of Heron will be bundled with a GCC binary for windows, and will actually generate executables.

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: Maybe I can answer Posted: Jan 18, 2006 7:20 AM
Reply to this message Reply
> Well the code snippets would behave less the same, but you lead me to an error
> I made. The Renamer object was a temporary, so b would have been left referring to
> a temorary Renamer object which would have been deleted at the end of the statement.

Yeah, I though of that too, but I dismissed it as not probable (because of the way you used it) and instead thought that you somehow made Renamer(a) to be allocated on heap.

def main() {
CA a;
TB b = Renamer(a);
b.whatever(); // runtime error (or worse, UB)
}


> > def main() {
> > CA x;
> > Renamer *y = new Renamer(x);
> > TB z = *y;
> >
> > delete y;
> >
> > //will this work?
> > z.b();
> > }

> The answer is not in the current version of HeronFront, but I plan on making
> it possible in later versions.

This is like the difference between heaven and hell. If programmer has to worry about correct lifetime of object pointed to by "y", then it will turn into a nightmare (even with strong/weak references). The programs are usually filled with examples like this (and usually hidden as function calls that accept pointer-to-interface), it will be impossible to do any big projects in Heron.

If it works, than that’s great. It's like adapter but better. I'm even considering if this is subtyping a-posteriori, but if it isn't, then it's very close.

Now, answer to your previous post:

> > method names of M1 and M2 don't match, but also M2 misses
> > some functions that M1 has (that can be easily
> > implemented given public interface of M2), and M1 misses
> > some functions that M2 has (that can be easily
> > implemented given public interface of M1).

> You said they are the same type, but with different methods can you really say
> they are the same type? Perhaps they share characteristics and internal structure,
> but I can't see how one can claim they are the same type, or subtypes of each other.

Maybe I could have said that M1 is inherently subtype of M2 (and vice versa). "Inherently subtype" means that M1 could be made to be subtype of M2 by adding subtype specification and writing missing methods. For example:

class StackA
{
//.. fields
public:
void pop() { … }
int top() { … }
void push(int element) { … }
};

class StackB
{
//.. fields
public:
int pop_and_return() { … }
void push(int element) { … }
};
Now, StackB isn't subtype of StackA (yet). But I could make it to be, if I had proper language facilities. I could write this (notice the keyword "augments"):

augment StackB : public StackA
{
public:
void pop() { pop_and_return(); }
int top() {int x=pop_and_return(); push(x); return x; }
};

Also, this is how I would like subtyping a-posteriori to look like.

> Anyway I see no problem here, I would use a wrapper trait like I describe above.
> A wrapper trait can change, or even introduce new behaviour.

[example cut]
This looks good. I have two minor objections (not too important):
- Programmer must explicitly write T2toT1(y) each time he needs the conversion. Automatic conversion would be better (less code=less bugs).
- If M1 has subtypes then you have to write adapter similar to trait T2toT1 for every subtype of M1. If you take my example above with stack and "augments" keyword, there not only does StackB get augmented, but also all of the subtypes of StackB.

> > First thing is that I like renaming.

>Renaming is completely possible within the proposed Heron traits system:

[example cut]
I can think of too much problems with renaming as in your example, but I could also say that Heron does have some basic renaming, and stop there. It's no problem for me to continue this discussion, if you want, but I don’t know is this renaming issue important to you or not. But I would like to summarize this whole trait discussion like this:

With planned additions, I think, it will be good. Probably better than C++. There are some issues that could be looked after, but nothing too serious.

Oh, and I forgot to answer first question:

> You keep talking about comparing the traits mechanism
> I am proposing with multiple inheritance. Inheritance and
> subtyping are fundamentally different ideas. Any subtyping
> mechanism which depends on inheritance is flawed because it
> results in undesirable structural dependencies between subtypes.

We use slightly different terminology. When I say inheritance, I mean both inheritance of implementation and subtyping(inheritance of interface). I understand the difference between inheritance of implementation and subtyping perfectly, and I agree they are fundamentally different ideas.

What happens with subtyping is that, in most cases, programmer wants to inherit implementation along. In order to save typing, this can be done as default by the programming language.

But, I absolutely agree that, if programmer wants it, he must be able to separate subtyping from inheritance of implementation. C++ doesn't allow this, but the problem is circumvented by creating multiple classes, one for interface, one for implementation and maybe more. This is, of course, tedious.

I think you are wrong when saying that subtyping in multiple inheritance depends on inheritance of implementation. This is just the way contemporary languages do it. It is easy to completely separate subtyping and inheritance of implementation with a few additional keywords. I even made a concrete proposal; you can read it here, with a few examples:

http://www.artima.com/forums/flat.jsp?forum=106&thread=142527&start=17&msRange=15

To conclude, I think you are wrong: multiple inheritance does not inherently depend on inheritance of implementation, so it is not flawed.

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: Maybe I can answer Posted: Jan 18, 2006 7:35 AM
Reply to this message Reply
I wrote:

> If M1 has subtypes then you have to write adapter similar to trait T2toT1 for every subtype of M1.

Which is wrong. And it's nice that you don't have to.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Maybe I can answer Posted: Jan 18, 2006 8:20 AM
Reply to this message Reply
> Now, StackB isn't subtype of StackA (yet). But I could
> make it to be, if I had proper language facilities. I
> could write this (notice the keyword "augments"):
[snip]

This is a very interesting idea, and I think you should write about it in more detail and post it online (do you have a blog?).

> This looks good. I have two minor objections (not too
> important):
> - Programmer must explicitly write T2toT1(y) each time he
> needs the conversion. Automatic conversion would be better
> (less code=less bugs).

Automatic conversion in general sometimes leads to more bugs. In my first years of using C++ (and Visual Basic) automatic numeric casts used to be a great source of trouble for me (and many other programmers). In this particular case I don't know whether implicit trait adaption would scale well. It is definitely worth keeping in mind. I would rather play it safe for now, and if there is a public outcry for the functionality in the future I will incorporate it.

> - If M1 has subtypes then you have to write adapter
> similar to trait T2toT1 for every subtype of M1. If you
> take my example above with stack and "augments" keyword,
> there not only does StackB get augmented, but also all of
> the subtypes of StackB.

Whether or not I incorporate it, I do find the idea very elegant.

> > > First thing is that I like renaming.
>
> >Renaming is completely possible within the proposed Heron
> traits system:
>
> [example cut]
> I can think of too much problems with renaming as in your
> example, but I could also say that Heron does have some
> basic renaming, and stop there. It's no problem for me to
> continue this discussion, if you want, but I don’t know is
> this renaming issue important to you or not.

At the moment, no. But in the future I would like to pick-up the discussion.

> like to summarize this whole trait discussion like this:
>
> With planned additions, I think, it will be good. Probably
> better than C++. There are some issues that could be
> looked after, but nothing too serious.

That is nice to hear. Too few people can discuss and debate, and actually say nice things at the same time. I can agree that there is still some work to do.

> We use slightly different terminology. When I say
> inheritance, I mean both inheritance of implementation and
> subtyping(inheritance of interface). I understand the
> difference between inheritance of implementation and
> subtyping perfectly, and I agree they are fundamentally
> different ideas.
>
> What happens with subtyping is that, in most cases,
> programmer wants to inherit implementation along.

I can't agree (nor can I really disagree). Programmers for the most part simply don't know that there is a difference and don't have any other options available to them.

> In order
> to save typing, this can be done as default by the
> programming language.
>
> But, I absolutely agree that, if programmer wants it, he
> must be able to separate subtyping from inheritance of
> implementation. C++ doesn't allow this,
>
> but the problem is
> circumvented by creating multiple classes, one for
> interface, one for implementation and maybe more. This is,
> of course, tedious.

Sounds like a description of Heron: traits are for describing interfaces, classes are for describing implementation. ;-)

> I think you are wrong when saying that subtyping in
> multiple inheritance depends on inheritance of
> implementation. This is just the way contemporary
> languages do it.

I will confess I am unfamiliar with any MI mechanism apart from the one embodied by C++.

> To conclude, I think you are wrong: multiple inheritance
> does not inherently depend on inheritance of
> implementation, so it is not flawed.

I stand corrected.

In the end I don't see how traits as described in Scharli's paper, are any different from MI as you define it.

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: Maybe I can answer Posted: Jan 18, 2006 10:09 AM
Reply to this message Reply
> This is a very interesting idea, and I think you should write about it in more detail
> and post it online (do you have a blog?).

No, I don't have a blog. I'm worried that no one would listen and that I wouldn't know how to get audience. I think that some of my ideas could help improve programming a lot, but if noone reads them then it's just a waste of time. And I have lots of interesting things to say. Maybe you can suggest me where I could post such things?

I might try with scientific community, posting a paper to some IEEE group (not about this, about something more interesting), but that requires more careful work than blog, and someone to check spelling and add all indefinite articles that I forget :), and someone to preview the paper who understands what I'm talking about (noone at my university has a clue).

Also, this "augments" example requires a sophisticated linker, it can't be added straight to C++, nor to any of the languages that create vtable at compile time.

> Automatic conversion in general sometimes leads to more bugs. In my first
> years of using C++ (and Visual Basic) automatic numeric casts used to be a
> great source of trouble for me (and many other programmers). In this particular
> case I don't know whether implicit trait adaption would scale well. It is definitely
> worth keeping in mind. I would rather play it safe for now, and if there is a public
> outcry for the functionality in the future I will incorporate it.

For the automatic pointer casts where subtype relation is involved, they are 100% safe (at least with MI). Except for one case where cast is ambiguous, but it's still safe, it's just that compiler doesn't know which cast to make… I don’t know whether that case can happen with structural subtyping.

Anyway explicit cast is just a bit more code… nothing serious, but better without it.

> Sounds like a description of Heron: traits are for describing interfaces, classes are
> for describing implementation. ;-)

What I like is when interface and class can be written at the same place (to save code duplication), but if necessary there must be a way to separate them. I don’t think this is exactly the way Heron will do it, but hopefully it's not too much code duplication (maybe better name would be declaration duplication).

> In the end I don't see how traits as described in Scharli's paper, are any different
> from MI as you define it.

Well, it's really like this: There is MI as implemented in C++, and I think it lacks functionality. So I devised what improvements it needs to be perfect (yes I think I got it perfect, meaning it can do any subtype relation possible and also inherit implementation if it is possible). And then I found about traits, and I wondered "can traits do all that my perfect MI can"? Of course, I won't settle for less. But thing is that I'm not sure traits can do it, but you clarified some things for me. Maybe I'll re-read Scharli's paper, and try some pathological examples (it is unfortunate that I forgot some of them, because I was thinking about this 2 years ago. But I remember most pathological was when directed cycles are needed in subtype relation, you can't change type definitions because of libraries, and library is constructing that types in value container. Like M1 and M2 case. I was thinking to write about it, but I thought I would get flames only because everyone thinks subtype relation is DAG).

So the answer is: I don’t know (yet). Oh, wait, there is nominal subtyping... :) . But except that I don’t know.

And thanks for support. Now that you said that, I'm actually thinking again about writing something.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Maybe I can answer Posted: Jan 18, 2006 10:41 AM
Reply to this message Reply
> > This is a very interesting idea, and I think you should
> write about it in more detail
> > and post it online (do you have a blog?).
>
> No, I don't have a blog. I'm worried that no one would
> listen and that I wouldn't know how to get audience. I
> think that some of my ideas could help improve programming
> a lot, but if noone reads them then it's just a waste of
> time. And I have lots of interesting things to say. Maybe
> you can suggest me where I could post such things?

You may be able to get a blog here at Artima by writing to the webmaster. It took me a long time to build a community of readers, but when I came to Artima there was a lot of people.

> I might try with scientific community, posting a paper to
> some IEEE group (not about this, about something more
> interesting), but that requires more careful work than
> blog, and someone to check spelling and add all indefinite
> articles that I forget :), and someone to preview the
> paper who understands what I'm talking about (noone at my
> university has a clue).

This is the challenge of sharing ideas, doing so in a manner which makes sense to other people. This is something I am still struggling with.

> Also, this "augments" example requires a sophisticated
> linker, it can't be added straight to C++, nor to any of
> the languages that create vtable at compile time.

Maybe one of the ML languages? When the Heron "compiler" (a Heron to C++ preprocessor front-end packaged with GCC) comes out (any day now) you may want to consider hacking it to add your own features.

We could have a friendly language building competition. I would be just as happy if I could contribute to the success of another language, as I would be for my own. Anyway, please feel free to email me: cdiggins at gmail dot you know the rest.

> What I like is when interface and class can be written at
> the same place (to save code duplication), but if
> necessary there must be a way to separate them.

Just a helpful observation: interfaces/traits can be theoretically deduced from classes. I haven't decided yet whether Heron will support that, but it is one possible route.

> I don’t
> think this is exactly the way Heron will do it, but
> hopefully it's not too much code duplication (maybe better
> name would be declaration duplication).

An interface deduction mechanism may help solve that.

> > In the end I don't see how traits as described in
> Scharli's paper, are any different
> > from MI as you define it.
>
> Well, it's really like this: There is MI as implemented in
> C++, and I think it lacks functionality.

Agreed.

> So I devised what
> improvements it needs to be perfect (yes I think I got it
> perfect, meaning it can do any subtype relation possible
> and also inherit implementation if it is possible).

Perfection is likely to be a subjective observation, but you do seem to have a fairly robust/complete subtyping system in mind.

> And
> then I found about traits, and I wondered "can traits do
> all that my perfect MI can"? Of course, I won't settle for
> less. But thing is that I'm not sure traits can do it, but
> you clarified some things for me. Maybe I'll re-read
> Scharli's paper, and try some pathological examples (it is
> unfortunate that I forgot some of them, because I was
> thinking about this 2 years ago. But I remember most
> pathological was when directed cycles are needed in
> subtype relation, you can't change type definitions
> because of libraries, and library is constructing that
> types in value container. Like M1 and M2 case. I was
> thinking to write about it, but I thought I would get
> flames only because everyone thinks subtype relation is
> DAG).
>
> So the answer is: I don’t know (yet). Oh, wait, there is
> nominal subtyping... :) . But except that I don’t know.

You might want to take a look at the Mixin class mechanism that is used in the new Scala 2.0. You may find it interesting. http://scala.epfl.ch/nsc/files/Changes.pdf

> And thanks for support. Now that you said that, I'm
> actually thinking again about writing something.

I think you definitely have some worthwhile ideas, and I appreciate your support as well!

Max Lybbert

Posts: 314
Nickname: mlybbert
Registered: Apr, 2005

My opinion of multiple inheritance Posted: Jan 18, 2006 11:44 AM
Reply to this message Reply
Not that anybody is holding their breath to find out what I think, but that never stopped me before (g).

/* To conclude, I think you are wrong: multiple inheritance does not inherently depend on inheritance of implementation, so it is not flawed.
*/

The only "flaw" that I've had to agree could be a flaw in some cases is found in Scharli's thesis (http://www.iam.unibe.ch/~scg/Archive/PhD/schaerli-phd.pdf starting on page 25): that parent classes may define identical data structures (eg., same type and name) with different purposes. I personally haven't run into this often, but others may have.

The other flaw (that parent classes may define identical methods (eg., same return type and name) with different purposes) is a real problem, but no solution gets around this; not mixins, not Java interfaces, not even traits.

Given that I consider this the only flaw, I believe most cures (eg., Java's single inheritance) are worse than the disease.

Traits do offer a decent fix for this issue: define a trait that offers a "get_data_x" function, and require that all types accessed through that trait get "data_x" from *somewhere* but don't worry about where. If two traits require identical getter methods but want different values for x, you'll have to use the solution provided on page 31 of the thesis. It's the same solution you need in multiple inheritence.

BTW, the practice Stroustrup recommends in "The C++ Programming Language" is to create pure virtual classes (eg., interface only) to use as parent classes as a way of getting the needed interfaces. IIRC, he recommended this *before* Java came up with it's version of interfaces.

/* What happens with subtyping is that, in most cases, programmer wants to inherit implementation along.
*/

I agree that multiple inheritance is often abused, and that this is an abuse. I also agree that any feature can be abused. I also believe that most cures for this abuse are worse than the disease, and I have no drive to try them.

Then again, I can't shake the feeling that if I were designing a new language, I'd try to implement features that were resistant to abuse. That is, if experience showed many programmers abused multiple inheritance, I might try offering something less dangerous. Then again (being who I am), I might also permit multiple inheritance if you were willing to go to extra lengths to discourage programmers from abusing the feature. I believe the syntax "static_cast<Type>(object)" is based on this idea. And notice that "reinterpret_cast" is even longer.

Max Lybbert

Posts: 314
Nickname: mlybbert
Registered: Apr, 2005

Re: My opinion of multiple inheritance Posted: Jan 18, 2006 11:56 AM
Reply to this message Reply
Now, as for why I think traits are better than Java interfaces, mixins, or other "solutions."

First, most solutions get rid of the one flaw by forbidding inheritance of state. Interfaces do this, traits do this. Big whoop. Yes, it works. And, although I can find many objects that truly *are* two things (a car is both a vehicle and a money pit), most of the time you only need to keep state for one of those things. In that case, your can use single inheritance to keep track of the needed data members (to save the state), and interfaces to handle the rest. Then again, there are cases where you need state for both parents, and multiple inheritance handles this well.

But traits seem to offer the ability to effectively build up your inheritance tree from the other end. That is, inheritance lets you define A.method() to do something, then derive object B so that B.method() logs calls to A.method(), and then you can derive object C so that C.method() does contract verification before calling B.method(), etc.

But what if D.method() needs A.method() to do something new? That is, often you have to work in a stack -- the A base needs to be constructed before you can construct the extra B parts; and B needs destroyed before destroying A. And yes, you can do D-specific things, and then call C::method() (or super.method() in Java) to go up the chain.

But what if you actually need to reach into A.method()? You have a situation where something needs done *as* *part* *of* A::method(), but you're up in D. And, you perhaps you don't have source for A, or perhaps only D needs this. Unless somebody smarter can come up with a good solution, the only answer I can think of would be for D.method() to be rewritten to say "do D-specific things; instead of calling C::method(), do all these steps that happen to be in C::method, but by listing these steps, I keep control; ditto for B::method(); great, now do whatever steps need doing in A::method(), with my added steps." That's a maintenance issue.

If I'm reading correctly, traits offer the ability to build up the same tree, but the programmer would have the ability to rach into lower levels easier. Two programmers could use the same library code to handle state, but create different traits hierarchies so that their trees would be wildly different, and they could share this code between each other (so long as they shared the traits) without much difficulty.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: My opinion of multiple inheritance Posted: Jan 18, 2006 5:52 PM
Reply to this message Reply
I think your reading of traits is correct except that they are not normally proposed as the only inheritance mechanism. Normally you have single inheritance and traits, much like Java has single inheritance and interfaces. You also potentially have inner classes, e.g. Scala, which are another form of multiple inheritance.

The net result of having three inheritance mechinisms, single, trait, and inner, is that you choose the most appropriate one. Note with all three mechanisms you can reach up the heirarchy and call functions that are overridden. EG assuming Java was extended so that interfaces became traits (like Scala), consider a program that deals with Teacher and Student classes that both extend class People and also a class AssistantTeacher that is both a Student and a Teacher and therefore inherits from Person twice:
interface Person {
    String getName();
    String toString() { return "Person: " + getName(); }
}
 
class PersonClass implements Person {
    private final String name;
    Person( final String name ) { this.name = name; }
    String getName() { return name; }
    String toString() { return Person.super.toString(); }
}
 
interface Student extends Person {
    int getMark();
    String toString() { return "Student: " + getName() + ", Mark = " + getMark(); }
}
 
class StudentClass extends PersonClass implements Student {
    private final int mark;
    Student( final String name, final int mark ) {
        super( name );
        this.mark = mark;
    }
    int getMark() { return mark; }
    String toString() { return Student.super.toString(); }
}
 
interface Teacher extends Person {
    float getSalary();
    String toString() { return "Teacher: " + getName() + ", Salary = " + getSalary(); }
}
 
class TeacherClass extends PersonClass implements Teacher {
    private final float salary;
    Teacher( final String name, final float salary ) {
        super( name );
        this.salary = salary;
    }
    float getSalary() { return salary; }
    String toString() { return Teacher.super.toString(); }
}
 
interface AssistantTeacher extends Student, Teacher {
    String toString() { return "AssistantTeacher: " + getName() + ", Mark = " + getMark() + ", Salary = " + getSalary(); }
}
 
class AssistantTeacherClass extends StudentClass implements AssistantTeacher {
    private final float salary;
    AssistantTeacher( final String name, final int mark, final float salary ) {
        super( name, mark );
        this.salary = salary;
    }
    float getSalary() { return salary; }
    String toString() { return AssistantTeacher.super.toString(); }
}

Note how in the classes the toString method specifies which toString to use (it has two - one from Object via single inheritance and one from its trait). For the toString methods you can see how it can reach up and call a specified super, e.g. Person.super.toString() to call toString in Person as oppossed to super.toString() to call Object's toString(). 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.

In summary a trait is like a cross between an interface and multiple inheritance, taking the best features of each. Probably closer to an interface than normal multiple inheritance though and normally proposed in addition to single inheritance.

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: My opinion of multiple inheritance Posted: Jan 19, 2006 12:24 PM
Reply to this message Reply
> The other flaw (that parent classes may define identical methods (eg., same return type
> and name) with different purposes) is a real problem, but no solution gets around this; not
> mixins, not Java interfaces, not even traits.

> Traits do offer a decent fix for this issue: define a trait that offers a "get_data_x" function,
> and require that all types accessed through that trait get "data_x" from *somewhere* but
> don't worry about where. If two traits require identical getter methods but want different
> values for x, you'll have to use the solution provided on page 31 of the thesis. It's the same
> solution you need in multiple inheritence.

I don’t agree. Consider this example (Java code with additional rename keyword)

interface B1
{ 
public abstract int get();
}
 
interface B2
{ 
public abstract int get();
}
 
class D implements B1, B2
{
rename {
B2:get to alternateGet
}
int x;
int y;
D() {x=3;y=4;}
public int get() {return x;}
public int alternateGet () {return y;}
}
main()
{
	D obj=new D;
	B1 b1obj=obj;
        B2 b1obj=obj;
	B1obj.get(); // returns 3
        B2obj.get(); // returns 4           
}

Clean, simple, readable.

> Then again (being who I am), I might also permit multiple inheritance if you were
> willing to go to extra lengths to discourage programmers from abusing the feature.

I agree.

> First, most solutions get rid of the one flaw by forbidding inheritance of state.
> Interfaces do this, traits do this. Big whoop.
> Then again, there are cases where you need state for both parents, and multiple
> inheritance handles this well.

I'm arguing that inheritance of state (and/or implementation) should be optional. Not obligatory, not forbidden, but optional. It's easy to add that to MI.

> But what if D.method() needs A.method() to do something new?

You must be using Java. This requirement is easily added to MI, and, in fact, C++ already offers it (pure C++ code following):
struct A            { virtual method() {return 1;} };
struct B : public A { virtual method () {return 2;} };
struct C : public B { virtual method () {return 3;} };
struct D : public C { virtual method () {return A::method()+7;} }; 


> If I'm reading correctly, traits offer the ability to build up the same tree, but the
> programmer would have the ability to rach into lower levels easier. Two programmers
> could use the same library code to handle state, but create different traits hierarchies so
> that their trees would be wildly different, and they could share this code between each
> other (so long as they shared the traits) without much difficulty.

Give me an example of what you think traits can do better than MI, and I'll try to find MI solution that's better than trait solution.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: My opinion of multiple inheritance Posted: Jan 19, 2006 2:18 PM
Reply to this message Reply
The problem with MI and optionally allowing state or not is that it the default tends to be the wrong way round, i.e. inheriting state is the default where you really want inheriting implementation only (trait behaviour) to be the default. A possible solution is to use a code transformation so that in a Java like language:
class Person {
  String name;
  Person( String name ) { this.name = name; }
  String getName() { return name; }
  String toString() { return "Person: " + name; }
}

The above code after transformation into Java becomes:
interface Person$Interface {
  String getName();
  String toString();
}
 
final class Person$Trait {
  public static String toString( Person$Interface thiz ) { return "Person: " + thiz.getName(); }
}
 
class Person implements Person$Interface {
  private String name;
  Person( String name ) { this.name = name; }
  public String getName() { return name; }
  public String toString() { return Person$Trait.toString( this ); }
}

To use Person in the Java like language that allows MI there are three options, single inheritance, multiple trait inheritance, and multiple interface inheritance. Taking each in turn:
class SingleInheritance extends Person {
  SingleInheritance( String name ) { super( name ); }
}

Does not need transformation since single inheritance is fine (provided that a named super isn't used). But trait inheritance does need a transformation, e.g.:
class TraitInheritance extends traitonly Person { // key word traitonly allow MI of traits
  private String name; // you need to provide the state because only the trait is inherited
  TraitInheritance( String name ) { this.name = name; }
  public String getName() { return name; }
  // toString comes from trait
}

Becomes
class TraitInheritance implements Person$Interface {
  private String name;
  TraitInheritance( String name ) { this.name = name; }
  public String getName() { return name; }
  public String toString() { return Person$Trait.toString( this ); } // toString added
}

Interface inheritance needs transformation to eliminate new keyword and access to named super, e.g.:
class InterfaceInheritance extends interfaceonly Person { // key word interfaceonly allow MI of interfaces
  private String name; // you need to provide the state because only the interface is inherited
  InterfaceInheritance( String name ) { this.name = name; }
  public String getName() { return name; }
  public String toString() { return Person.super(); } // toString needed and note access to named super
}

Becomes
class InterfaceInheritance implements Person$Interface {
  private String name;
  InterfaceInheritance( String name ) { this.name = name; }
  public String getName() { return name; }
  public String toString() { return Person$Trait.toString( this ); } // named super transformed
}

There are three further points of note with this mythical Java like MI language:

1. Variable/field/argument declarations are transformed, e.g. Person p becomes Person$Interface p

2. The compiler may need to write get and set functions so that the trait can access state. This issue was not shown in the example since getName was explicity included in Person.

3. You would also probably want (but don't require) keywords to rename and exclude methods to prevent name clashes. You would probably want these extra keywords because it will be clear what you are doing. You don't require them since named supers allow access to anything and therefore normal methods can resolve any problem.

(These ideas, or at least similar, have been discussed in other forums on this site.)

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: My opinion of multiple inheritance Posted: Jan 20, 2006 2:03 AM
Reply to this message Reply
> There are three further points of note with this mythical
> Java like MI language:

I agree. Except on point 2 maybe, because that could be avoided with obligatory accessor keyword we talked about earlier.

> (These ideas, or at least similar, have been discussed in
> other forums on this site.)

Sorry, I'm new. But I like kind-of-discussion that happens at this site.

Flat View: This topic has 35 replies on 3 pages [ « | 1  2  3 | » ]
Topic: Generics and Packages Previous Topic   Next Topic Topic: What if Constant Values were also valid Types?

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use