The Artima Developer Community
Sponsored Link

Weblogs Forum
Eureka: Forget Interfaces what I really need are Traits!

21 replies on 2 pages. Most recent reply: Jan 8, 2006 11:38 PM by Howard Lovatt

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 21 replies on 2 pages [ « | 1 2 | » ]
Todd Blanchard

Posts: 316
Nickname: tblanchard
Registered: May, 2003

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Dec 29, 2005 12:56 PM
Reply to this message Reply
Advertisement
Oh, and before I forget:

http://www.iam.unibe.ch/~scg/Research/Traits/

Cleo Saulnier

Posts: 77
Nickname: vorlath
Registered: Dec, 2005

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Dec 29, 2005 1:28 PM
Reply to this message Reply
This was a little hard on the head from the syntax, although the first example looks exactly like an abstract template in C++ (which doesn't exist AFAIK). Well, I suppose you could have an abstract template, but what's the point? If your T can be used like a Variant, then maybe. With dynamic typing, I suppose it'd be useful... possibly even required.

In the first link, if it's an interface (Java style) that can have pre-written default methods, then I always wondered why this wasn't already available in languages such as Java for example. In C++, you can do this with a partially abstract class, but there's no inherent description in the language that this is meant as an interface. Is that what you're going for?

You can ignore my questions if they don't make sense. I've only briefly looked at Heron.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Dec 30, 2005 8:00 PM
Reply to this message Reply
First congratulations on an excellent post and I agree with you that Traita are a great idea. However Traits and mixins are different. In a trait you:

1. Can't define any fields, only methods. You can define an an abstract accessor function to a field though. IE a Trait is like a Java interface with optional definitions for the methods. A Mixin can have a field.

2. When a class implements multiple Traits and they have the same method you have to say which method will be used. In a mixin the order of mixing determins which method will be used.

3. With a Trait you can access all super Traits individually. With a mixin only the last overridden method can be called via super.

Therefore Ruby Modules are more Mixins than Trait since they don't follow any of the above points. However they are a bit Trait like in that if two fields are defined with the same name you get just one field that is shared (a conventional mixin would give you two fields). This is only a bit Trait like because Traits don't allow fields!

Insidently you can mimic Traits in C++ using templates and virtual inheritance.

PS I was wondering why you are developing a new langauge since many of the features you wish C++ has exist in Java or Scala and these already exist.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Dec 31, 2005 8:41 AM
Reply to this message Reply
> First congratulations on an excellent post and I agree
> with you that Traita are a great idea.

Thank you!

> However Traits and mixins are different.

Okay.

> In a trait you:
>
> 1. Can't define any fields, only methods. You can define
> an an abstract accessor function to a field though. IE a
> Trait is like a Java interface with optional definitions
> for the methods. A Mixin can have a field.
>
> 2. When a class implements multiple Traits and they have
> the same method you have to say which method will be used.
> In a mixin the order of mixing determins which method will
> be used.
>
> 3. With a Trait you can access all super Traits
> individually. With a mixin only the last overridden method
> can be called via super.

This sounds reasonable, I don't have a good definition of a Mixin available, can you point to any sites?

In general this is precisely what I expected of Traits and what I plan on adding to Heron. I plan on using Traits more or less as found in Scala, but with some modifications.

> Therefore Ruby Modules are more Mixins than Trait since
> they don't follow any of the above points. However they
> are a bit Trait like in that if two fields are defined
> with the same name you get just one field that is shared
> (a conventional mixin would give you two fields). This is
> only a bit Trait like because Traits don't allow fields!
>
> Insidently you can mimic Traits in C++ using templates and
> virtual inheritance.

Interesting, I have to look into that.

> PS I was wondering why you are developing a new langauge
> since many of the features you wish C++ has exist in Java
> or Scala and these already exist.

There are limitations of Java and Scala which are insurmountable for certain kinds of programming.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 1, 2006 5:01 PM
Reply to this message Reply
> This sounds reasonable, I don't have a good definition of
> a Mixin available, can you point to any sites?

http://www.iam.unibe.ch/~scg/Archive/PhD/schaerli-phd.pdf

> > Insidently you can mimic Traits in C++ using templates
> and
> > virtual inheritance.
>
> Interesting, I have to look into that.

Same reference again

The above reference is the original Trait work and compares Traits to Mixins and also shows how to mimic Traits using C++ templates. Traits are also very similar to the style of multiple inheritance that Coplien in Advanced C++ Programming Styles and Idioms advocated (section 5.7).

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 1, 2006 5:56 PM
Reply to this message Reply
> The above reference is the original Trait work and
> compares Traits to Mixins and also shows how to mimic
> Traits using C++ templates.

Thanks for the link, the original Trait work however appears to be:

[SCHA 03] N. Scharli, S. Ducasse, O. Nierstrasz, and A. Black. Traits: Composable Units of Behavior. In
Proceedings ECOOP 2003, pages 248–274. Springer Verlag, July 2003. (pp 16, 106) 126

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 2, 2006 1:43 AM
Reply to this message Reply
> Thanks for the link, the original Trait work however
> appears to be:
>
> [SCHA 03] N. Scharli, S. Ducasse, O. Nierstrasz, and A.
> Black. Traits: Composable Units of Behavior. In
> Proceedings ECOOP 2003, pages 248–274. Springer Verlag,
> July 2003. (pp 16, 106) 126

Schaerli is the PhD student who's thesis I referenced, it is the same work. As is usual the thesis author is credited to the student alone whereas the paper based on the PhD is credited to the student plus the supervisors. The differences in spelling are because Scharli has an accent on the 'a' that has been replaced by ae in the alternate spelling.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 2, 2006 10:17 AM
Reply to this message Reply
> Schaerli is the PhD student who's thesis I referenced, it
> is the same work. As is usual the thesis author is
> credited to the student alone whereas the paper based on
> the PhD is credited to the student plus the supervisors.
> The differences in spelling are because Scharli has an
> accent on the 'a' that has been replaced by ae in the
> alternate spelling.

Thank you for clearing that up for me.

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 4, 2006 5:11 PM
Reply to this message Reply
I don't understand, it seems to me that multiple inheritance can do anything that traits can, except that with traits you can make errors by mixing incompatible traits, which cannot be done with multiple inheritance.

In article "Traits: Composable Units of Behavior", authors mention following difficulties with multiple inheritance:
- conflicting features
- accessing overridden features
which have absolutely nothing to do with multiple inheritance, and can be avoided by simple introduction of renaming.

Why do you like traits?
And why do you like structural subtyping? It is really easy to argue against it.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 4, 2006 5:36 PM
Reply to this message Reply
> I don't understand, it seems to me that multiple
> inheritance can do anything that traits can,

Maybe you're right, I'm not sure. But either way I prefer the Heron approach.

> Why do you like traits?

They are very flexible and reusable.

> And why do you like structural subtyping? It is really
> easy to argue against it.

To be precise I like behavioral subtyping. By this I mean the ability to express and verify preconditions, postconditions and invariants from within the trait. This is much more powerful than structural subtyping alone, and completely negates all the arguments against structural subtyping which I am familiar with.

Are there any issues with this method of subtyping which you are familiar with?

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 4, 2006 8:50 PM
Reply to this message Reply
My objections on structural subtyping boil down to name ambiguities.

When using inheritance (nominal subtyping), you explicitly specify subtyping relationship. Compared with structural subtyping, this is a small increase of program code, and delivers a great benefit that compiler can statically check for some serious errors. Inheritance isn't more restrictive than structural subtyping in any way (and vice versa).

I even think that a language having structural subtyping can not be called "strongly typed".

For example:
struct SimpleQueueImpl[type Element] {
...
def push(Element x) {...}
def pop() : Element {...}
def is_empty() : bool {...}
...
}

trait ReversesStack[type Element] {
requires {
def push(Element x);
def pop() : Element;
def is_empty() : bool;
}
public {
def reverse() {
//reverses stack by popping and then pushing all elements
}
}

Now, someone can accidentally add ReversesStack trait to SimpleQueueImpl. This, of course, won't work properly. The error is simple to spot in this example because of suggestive names, but that may not be the case for big projects with many abstract names. Inheritance specifically prevents you to do this.

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 5, 2006 1:21 PM
Reply to this message Reply
I want to further explain some ideas from my previous post (before you ask me those very same things).

First, the example I provided is really an example for mixins vs traits, but nominal vs structural subtyping is in fact another manifestation of the same underlying problem. I think that it is easy to figure out how above example is also the case against structural subtyping.

The another thing is that Heron implements behavioral subtyping, while I keep talking about structural subtyping. Well, behavioral subtyping helps to prevent some errors that structural subtyping can't catch. But it does not catch all errors. And there are problems.

For example, behavioral subtyping could detect the error in my previous example if postcondition y=pop(); assert(x==y); push(y) is added to push() in ReversesStack.

Now my case against behavioral subtyping. First, and the most important, it catches errors at runtime while inheritance catches them at compile time. This alone should be enough. The only disadvantage of inheritance is a small increase of code size, which is a small price to pay for this benefit.

Second behavioral subtyping has problems. It detects error using postconditions and invariants. But
- you can not always write postconditions in a manner orthogonal to original algorithm
- it might not be possible to verify all postconditions given some specific interface.
- although postconditions are generally a good thing, there are many, many situations (I estimate at least 50% of all functions) where it might not be desirable to write all postconditions because of increased development time vs little benefit gained.
- postconditions can actually be many times larger than function that they are checking. This is due to various dependencies between functions and state variables.

And similar arguments for invariants….
Not to be misunderstood, I would just like to mention that I support design by contract, especially preconditions.

Now, are you really ready to introduce problems in your language, just to save programmers from typing a few lines of subtyping specification?

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 5, 2006 5:08 PM
Reply to this message Reply
@Kresimir Cosic

I like traits but rather than defend them in a standard way I was going to give some an example and compare to multiple inheritance. I hope you can find time to say why you prefer multiple inheritance based on the example below.

Lets assume that Java did multiple inheritance and like say Eiffel you can rename a method/field or exclude a method/field and that a convention existed for access to multiple super classes. The example is a university department that has people in it and the people can be students, teachers, or teaching assistants (which are students that are paid to do some teaching - i.e. they are both a Student and a Teacher). Note the example doesn't use renaming but does use exclude and multiple super types. First using multiple inheritance.
class Person {
    private final String name;
    Person( final String name ) { this.name = name; }
    String getName() { return name; }
    String toString() { return "Person: " + name; }
}
 
class Student extends Person {
    private final int mark;
    Student( final String name, final int mark ) {
        super( name );
        this.mark = mark;
    }
    int getMark() { return mark; }
    String toString() { return "Student: " + getName() + ", Mark = " + mark; }
}
 
class Teacher extends Person {
    private final float salary;
    Teacher( final String name, final float salary ) {
        super( name );
        this.salary = salary;
    }
    float getSalary() { return salary; }
    String toString() { return "Teacher: " + getName() + ", Salary = " + salary; }
}
 
class AssistantTeacher extends
    Student, 
    Teacher exclude { name } // attempt at only one name!
{
    AssistantTeacher( final String name, final int mark, final float salary ) {
        Student.super( name, mark );
        Teacher.super( null, salary ); // note name excluded
    }
    String toString() { return "AssistantTeacher: " + getName() + ", Mark = " + getMark() + ", Salary = " + getSalary(); }
}

The main difference between a Trait and multiple inheritance are the fields. Here in particular the name field. We only want one name field in AssistantTeacher so we exclude it from the Teacher inheritance path and pass null to the Teacher constructor where the name field is. But there are two problems with this:

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?

2. How did the writer of AssistantTeacher know that the field to exclude was called name? The field is private in Person!

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:
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 and one from its trait).

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.

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).

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.

Kresimir Cosic

Posts: 34
Nickname: kreso1
Registered: Jan, 2006

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 5, 2006 8:17 PM
Reply to this message Reply
> 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:

class AssistantTeacher extends
    Student, 
    Teacher exclude implementation
{
        private final float salary;
        AssistantTeacher( final String name, final int mark, final float salary ) {
        Student.super( name, mark );
        this.salary = salary;
    }
    String toString() { return "AssistantTeacher: " + getName() + ", Mark = " + getMark() + ", Salary = " + getSalary(); }
}


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.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Jan 5, 2006 10:24 PM
Reply to this message Reply
> 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.

Flat View: This topic has 21 replies on 2 pages [ « | 1  2 | » ]
Topic: Are your loops volatile controlled? Previous Topic   Next Topic Topic: Mixins2

Sponsored Links



Google
  Web Artima.com   

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