Programming  /  Scala August 26, 2010

Learning Scala part seven – Traits

In this part of the Learning Scala series we’ll look at traits which is one of the features of Scala that appeals the most to me as a C# developer.

Traits can be thought of as interfaces in which it’s possible, but not necessary, to provide implementations for all or some of the methods that the interface defines. Or, by looking at traits from a different perspective we can also think of them as classes that can’t have constructor parameters. We can also think of the concept as multiple inheritance without some of it’s dangers but with some limitations.

An example with birds

Let’s look at a simple and contrived example. We have an abstract class for birds with a single field, flyMessage, and two methods, fly and swim.

abstract class Bird {
    def flyMessage: String
    def fly() = println(flyMessage)
    def swim() = println("I'm swimming")
}

The flyMessage field is abstract. Note that we don’t have to mark it as abstract, we just skip implementing it. However, then the compiler can’t infer it’s type so we have to specify that.

Both of the methods have concrete implementations. The fly method prints the value of the flyMessage field while the swim message prints a constant string. We also have a couple of concrete bird classes, Pigeon and Hawk.

class Pigeon extends Bird {
    val flyMessage = "I'm a good flyer"
}

class Hawk extends Bird {
    val flyMessage = "I'm an excellent flyer"
}

Using these we can create a couple of birds and let them fly around in the console.

val birds = List(
    new Pigeon,
    new Hawk)

birds.foreach(bird => bird.fly())

As you might suspect the scripts prints the following when executed:

I'm a good flyer
I'm an excellent flyer

Enter the penguin

So far so good. However, let’s say that we wanted to create a Penguin class. Penguins don’t fly.

penguin

Cute, but can’t fly.

How do we handle that? We could create a new abstract class, FlyingBird, that has the fly method and inherits from Bird. Pigeon and Hawk could inherit from that while Penguin would inherit from Bird. This way we keep our code DRY and we wouldn’t have to throw any nasty NotImplementedExceptions or such in the fly method. Using that approach our class hierarchy would look like this:

UML diagram for the Birds

The Frigatebird shows up to complicate things

Now, let’s say that we also wanted to add a class that represents the majestic Frigatebird, a species of bird that can’t swim. 

Frigate-bird

Frigatebirds have the largest wingspan to body ratio of all birds, but they can’t swim.

How do we make the Frigatebird class fit into the class hierarchy that we looked at to accommodate penguins? It most certainly can fly so inheriting from FlyingBird would be nice, but that would mean that it would have the swim method so that won’t work. Neither would inheriting from Bird as that’s the class that has the swim method. We could fix that by creating a new abstract class, SwimmingBird, that we move the swim method to. Both FlyingBird and Penguin could then inherit from SwimmingBird while FrigateBird inherits from just Bird. But then FrigateBird wouldn’t have a fly method. In Java or C# we could create an interface that defined the fly method, let both FlyingBird and Frigatebird implement that using either the same (copy/pasted) code or using some sort of utility class. While that approach would technically work it usually leads to having a lot of interfaces and a lot of repeated code, all to satisfy a weird inheritance hierarchy that is much due to the lack of support for multiple inheritance in the language. Luckily for us, Scala has traits.

Let’s begin by creating a trait for birds that can swim, Swimming, to which we move the swim method.

trait Swimming {
    def swim() = println("I'm swimming")
}

Our Bird class, without the swim method, now looks like this:

abstract class Bird {
    def flyMessage: String
    def fly() = println(flyMessage)
}

To ensure that pigeons and hawks can still swim we mix in the Swimming trait in their class definitions:

class Pigeon extends Bird with Swimming {
    val flyMessage = "I'm a good flyer"
}

class Hawk extends Bird with Swimming {
    val flyMessage = "I'm an excellent flyer"
}

Traits are added to a class using the with keyword given that the class has a super class. However, if the class doesn’t inherit from another class the first trait that is added to it is added using the extends keyword instead of the with keyword.

As we’re now able to create birds that don’t necessarily know how to swim we can now create our Frigatebird class.

class Frigatebird extends Bird {
    val flyMessage = "I'm an excellent flyer"
}

Just like Pigeon and Hawk it inherits from Bird but the Frigatebird class doesn’t have the Swimming trait.

Next we’ll create a trait for flying to which we move both the fly method and the flyMessage field. The trait, and the now very simple Bird class, looks like this:

abstract class Bird

trait Flying {
    def flyMessage: String
    def fly() = println(flyMessage)
}

We can now create the Penguin class as we’re able to create birds that don’t know how to fly.

class Penguin extends Bird with Swimming

class Pigeon extends Bird with Swimming with Flying {
    val flyMessage = "I'm a good flyer"
}

class Hawk extends Bird with Swimming with Flying {
    val flyMessage = "I'm an excellent flyer"
}

class Frigatebird extends Bird with Flying {
    val flyMessage = "I'm an excellent flyer"
}

We can now let all of the birds that know how to fly fly and all of the birds that know how to swim swim.

val flyingBirds = List(
    new Pigeon,
    new Hawk,
    new Frigatebird)

flyingBirds.foreach(bird => bird.fly())

val swimmingBirds = List(
    new Pigeon,
    new Hawk,
    new Penguin)

swimmingBirds.foreach(bird => bird.swim())

If we however were to put a Penguin in the list of birds that can fly we would get a compilation error saying that “value fly is not a member of this.Bird” as the compiler would infer that that flyingBirds list contains instances of the Bird class without any traits mixed in as that is the least common denominator for all of the objects in the list.

Mixing in traits upon instantiation

Suppose we removed the Swimming trait from the Pigeon class, instantiated a Pigeon and asked it to fly.

class Pigeon extends Bird with Flying {
    val flyMessage = "I'm a good flyer"
}

val pigeon = new Pigeon
pigeon.swim()

Naturally we would get a compilation error as instances of the Pigeon class wouldn’t have such a method.

error: value swim is not a member of this.Pigeon
pigeon.swim()
       ^
one error found

Besides the obvious fix of adding the Swimming trait to the class declaration again we could also create a Pigeon that incorporates the Swimming trait upon instantiation.

class Pigeon extends Bird with Flying {
    val flyMessage = "I'm a good flyer"
}

val pigeon = new Pigeon with Swimming
pigeon.swim()

The above script compiles fine and produces the expected output.

What about the Flying trait? Could we do the same with that? It depends. As the Flying trait contains the abstract flyMessage field the class that we incorporate it into needs to define that. In other words will the code below work:

class Pigeon extends Bird with Swimming {
    val flyMessage = "I'm a good flyer"
}

val pigeon = new Pigeon with Flying
pigeon.fly()

But if we remove the flyMessage field in the Pigeon class…

class Pigeon extends Bird with Swimming

val pigeon = new Pigeon with Flying
pigeon.fly()

…we get a compilation error.

error: object creation impossible, since method flyMessage in trait Flying of type => String is not defined
val pigeon = new Pigeon with Flying
                 ^
one error found

More about traits

We thank the birds in the example for showing us some basic usage of traits. There are a few more basic things about traits that I think is good to know that the example didn’t show us though.

Constructors

Traits can’t have auxiliary constructors and the primary constructor can’t have parameters. Also, the primary constructor (the body of the trait) can’t pass values to a super type’s constructor.

Inheritance

Traits can inherit from other traits or from classes. However, as traits can’t pass values to their parent type’s constructors they can only extend classes that have a parameterless constructor (either primary or auxiliary).

The abstract keyword

As we saw in the example we can have both abstract and concrete members in a trait. If we have only abstract members and do nothing in the constructor the trait is very much like an interface in Java or C#.

You might have noticed that we didn’t use the abstract keyword when we declared the abstract flyMessage member in the Flying trait in the example. The abstract keyword isn’t necessary as the compiler can figure it out from its lack of initialization (or lack of implementation for methods).

Linearization

As traits offer a form of multiple inheritance the inheritance hierarchy for a type isn’t necessarily linear but forms an acyclic graph which will need to be “flattened” upon compilation. This is a bit too advanced for this post but I might revisit the topic in a later post. If you want to learn more about this topic right away you might want to check out Jim McBeath’s post Scala Class Linearization.

Recap and a look at what’s next

Traits provide a lot of the goodness that multiple inheritance without much of it’s problems, of course for the price of a few limitations. For me as primarily a C# developers traits look extremely useful for solving problems that in C# often require me to either duplicate code, create weird helper/utility classes or use fairly complex solutions using Aspect Oriented Programming.

This post is of course just a basic introduction to traits and there is more to learn about them. Two good starting points for doing so is probably A Tour of Scala: Traits and A Tour of Scala: Mixin Class Composition.

In the next part we’ll take a look at Scala’s type hierarchy.

About this post and the Learning Scala series

This post is a part of a series of posts in which I describe my experiences while trying to learn Scala. I try to do it in the form of a tutorial as I find doing so is an excellent way of consolidating knowledge and hopefully I can also help others. However, keep in mind that I’m in no way an expert on Scala. This is just a way to document what I’ve learned so far and there might be some things that I’ve misunderstood. If you’ve found such a thing and would like to help me correct that misunderstanding, or if you would like to leave any other kind of feedback don’t hesitate to leave a comment!

You can find links to all posts in this series at the bottom of the first post.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

More about Scala