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 | » ]
Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Eureka: Forget Interfaces what I really need are Traits! (View in Weblogs)
Posted: Dec 29, 2005 9:35 AM
Reply to this message Reply
Summary
I was working on specifying separate mixin and interface types, when I remembered Scala Traits / Ruby Modules.
Advertisement
It just dawned on me as I was working on a mixin specification, that the Ruby and Scala solutions are far more elegant than what I was moving towards. Thanks to Keith Gaughan for jogging my memory on this point.

I am less familiar with Ruby Modules ( http://www.rubycentral.com/book/tut_modules.html ) , but as far as I can tell it is pretty much the same thing as the trait in Scala ( http://scala.epfl.ch/intro/traits.html ) : essentially a stateless class which can be used as a mixin, and as an interface (I welcome any corrections).

In one fell swoop this solves several of my problems. I was heading towards several different constructs, which in the end can all be achieved using traits.

Here is a simple interface implemented using a trait:

  trait StackInterface[T : type] {
    signature {
      push(T x);
      pop() : T;
      is_empty() : bool;
    }  
  }
One thing very exciting for me about traits is that they can be used to implement contracts:
  trait StackContract {
    signature {
      push(T x) {
        override;
        assert(!is_empty()); 
      } 
      pop() : T {
        assert(!is_empty()); 
        override;
      }
    }
  }
However, with one small change to the language specification: _before and _after member macros (incidentally something similar existed in the original cfront tool by Stroustrup), you can implement an AOP style advice class.
  trait LoggingAdvice {
    macros {
      _before = {
        writeln("calling function " + _function_name);
      }
    }
  }
So now you can design an extremely powerful and flexible class using a composition of mixins.
  typedef Stack[type T] = 
    mixin(LoggingAdvice, 
      mixin(StackContract[T], heron::stack[T]));
That is only the tip of the iceberg, conditional mixin statements would make things even more exciting:
  static const bool bLogging = true; 
  static const bool bVerifyContracts = true; 

  typedef Stack[type T] = 
    mixin_if(bLogging, LoggingAdvice, 
      mixin_if(bVerifyContracts, StackContract[T], heron::stack[T]));
How do you like them apples?


Todd Blanchard

Posts: 316
Nickname: tblanchard
Registered: May, 2003

Re: Eureka: Forget Interfaces what I really need are Traits! Posted: Dec 29, 2005 12:54 PM
Reply to this message Reply
Ya know, I think I've mentioned and pointed to the traits paper half a dozen times in your various rants. I'm glad to see you've finally taken a look.

Screw interfaces and abstract base classes - traits is much more sensible.

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

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