From F# to Scala — traits

Read the whole series:

Part 1 — type infer­ence

Part 2 — traits <- you’re here

Part 3 — case class/object (ADTs)

Part 4 — apply & unap­ply func­tions

Part 5 — implic­its


Con­tin­u­ing on from where we left off with type infer­ence last time around, let’s look at a lan­guage fea­ture in Scala that doesn’t exist in F# — traits.

Scala has both abstract class­es and traits (think of them as inter­faces, but we’ll get into the dif­fer­ences short­ly) to sup­port OOP. Abstract class­es are exact­ly what you’d expect and the pre­ferred option where Java-interop is con­cerned. Traits, how­ev­er, are much more flex­i­ble and pow­er­ful, but with great pow­er comes great respon­si­bil­i­ty.

 

Basics

Like abstract class­es, they can con­tain both fields and behav­iour, and both abstract def­i­n­i­tions and con­crete imple­men­ta­tions.

Any class that extends from this trait will inher­it all the con­crete imple­men­ta­tions and need to imple­ment the abstract mem­bers.

Of course, the con­crete class can also over­ride the default imple­men­ta­tion that came with the trait.

You can extend mul­ti­ple traits.

wait, hold on a sec, what’s this object thingy­ma­jig?

Sor­ry for spring­ing that on you! A Scala object is basi­cal­ly a sin­gle­ton and is Scala’s equiv­a­lent to a F# mod­ule. In fact, when you define an object in the Scala REPL it actu­al­ly says “defined mod­ule”!

The notable dif­fer­ence with a F# mod­ule is that a Scala object can extend abstract class­es and/or traits (but itself can­not be extend­ed by anoth­er class/object). We’ll spend more time drilling into object lat­er in the series but I just want­ed to throw it in here for now as it’s such a heav­i­ly used fea­ture.


The key thing to take away from the snip­pet above is that you can extend a class or object with mul­ti­ple traits.

 

Traits vs Abstract Classes

There are 2 dif­fer­ences between traits and abstract class­es:

  1. traits can­not have con­trac­tor para­me­ters but abstract class­es can
  2. you can extend mul­ti­ple traits but can only extend one abstract class

With regards to point 2, you can actu­al­ly mix both traits and abstract class­es togeth­er!

 

Dealing with Collisions

Since you can extend more than one thing at a time (be it mul­ti­ple traits, or 1 abstract class + 1 or more traits), one must won­der what hap­pens when some of the mem­bers col­lide.

You might have noticed from our last snip­pet that both Being and Human defines a name field, but every­thing still works as expect­ed and you can indeed use the the­burn­ing­monk object as a Human or Being.

Ok. What if I extend from 2 traits with clash­ing mem­bers and one of them pro­vides a con­crete imple­men­ta­tion? My expec­ta­tion would be for the con­crete imple­men­ta­tion to fill in for the abstract mem­ber with the same name.

and indeed it does.

What if I’m mix­ing traits with an abstract class?

No prob­lem, still works.


side note: notice that in this sec­ond ver­sion Pro­fes­sorX is extend­ing Psy­chic first? That’s because in cas­es where abstract class and traits are both involved, only the traits can be mixed in.


So far so good, but what if both traits/abstract class­es pro­vide a con­crete imple­men­ta­tion for a clashed mem­ber?

The safe thing to do here would be for the com­pil­er to crap out and force the devel­op­er to rethink what he’s doing rather than spring­ing a nasty sur­prise lat­er on.

(and yes, it behalves the same way if Light or Dark is an abstract class instead)

This, in my opin­ion is a much bet­ter way to resolve col­li­sions than the Python way.

 

Dynamic Composition

So far we have mixed in traits when defin­ing a new class or object, but you can do the same in a more ad-hoc fash­ion.

Of course, all the rules around col­li­sion res­o­lu­tion also hold true here.

 

Generics

Whilst traits can­not have con­struc­tor para­me­ters, they can have type para­me­ters (ie, they are gener­ic!).

To sneak­i­ly weave in the Trans­form­ers game I’m work­ing on at Space Ape Games, here’s how you might mod­el a Trans­former Com­bin­er as a gener­ic trait.

 

Stackable Modifications

When you read about traits in Scala, you often hear the phrase “stack­able mod­i­fi­ca­tions” (Jonas Boner’s real-world Scala slide deck has a nice exam­ple).

What makes this inter­est­ing (and dif­fer­ent from straight up over­ride in inher­i­tance) is how super is mod­i­fied in an ad-hoc fash­ion as you com­pose an object using traits (see Dynam­ic Com­po­si­tion sec­tion above).

This also works if Mutant is an abstract class, and in the def­i­n­i­tion of an object too.

How­ev­er, it only work with meth­ods. If you try to over­ride an immutable val­ue then you’ll get a com­pil­er error.

And no, it doesn’t work with vari­able either, only meth­ods.

 

self type annotation

Anoth­er relat­ed top­ic is the so-called self type anno­ta­tion fea­ture. It is often used to imple­ment depen­den­cy injec­tion in Scala with pat­terns such as the Cake Pat­tern.

Where you see code such as below (note the self : A => ), it means the trait B requires A.

Any com­pos­ing object/class would need to mix in trait A if they want to extend trait B, fail­ure to do so will be met with a swift and dead­ly com­pil­er error.

This also gives the trait B access to mem­bers from A (which includes any mem­bers A inher­its). For instance.

What’s more, you can mix in mul­ti­ple traits in the self type.

It’s worth dif­fer­en­ti­at­ing this “requires” rela­tion­ship from the “is a” rela­tion­ship cre­at­ed through inher­i­tance.

In this case, since the Rooney trait is not a Foot­baller and Record­Hold­er (which he is in real life, of course) it won’t inher­it the mem­bers from those traits either.

 

Inter­est­ing­ly, it’s pos­si­ble to cre­ate cyclic depen­den­cies using self type

which is not pos­si­ble through inher­i­tance.

As a .Net devel­op­er who have seen the dam­ages cyclic ref­er­ences can do, I’m slight­ly con­cerned that you could do this in Scala…

That said, in Mar­tin Oder­sky’s Pro­gram­ming in Scala, he has an exam­ple of how this mutu­al depen­den­cy can be use­ful in build­ing a spread­sheet, so I’ll keep an open mind for now.

…a new trait, Eval­u­a­tor. The method needs to access the cells field in class Mod­el to find out about the cur­rent val­ues of cells that are ref­er­enced in a for­mu­la. On the oth­er hand, the Mod­el class needs to call eval­u­ate. Hence, there’s a mutu­al depen­den­cy between the Mod­el and the Eval­u­a­tor. A good way to express such mutu­al depen­den­cies between class­es was shown in Chap­ter 27: you use inher­i­tance in one direc­tion and self types in the oth­er.

In the spread­sheet exam­ple, class Mod­el inher­its from Eval­u­a­tor and thus gains access to its eval­u­a­tion method. To go the oth­er way, class Eval­u­a­tor defines its self type to be Mod­el, like this:

  package org.stairwaybook.scells
  trait Evaluator { thisModel => ...

 

Final­ly, as you can see from Mar­tin Oder­sky’s exam­ple above, you don’t have to use “self” as the name for the self instance.


side note: I’m curi­ous as to how Scala deals with a cyclic depen­den­cy where the traits define val­ues that depends on a val­ue on the oth­er trait.

as you can see from all that red, even the code analy­sis tool in Intel­liJ gave up try­ing to under­stand what’s going on, but it com­piles!

What’s inter­est­ing (and again, slight­ly wor­ry­ing) is when the fields are eval­u­at­ed, which actu­al­ly depends on the order the traits are mixed in.

(new Object with Yin with Yang).motto 

  1. Yin is mixed in, yang.motto is not yet ini­tialised (and there­fore null) so Yin.phrase is ini­tialised as null + “yin”
  2. Yang is mixed in, yin.phrase is ini­tialised to “nul­lyin”, so Yang.motto is now “nul­lyin” + “yang”
  3. the val­ue of mot­to is there­fore nul­lyinyang

(new Object with Yang with Yin).motto 

  1. Yang is mixed in, yin.phrase is not yet ini­tialised, so Yang.motto is ini­tialised as null + “yang”
  2. Yin is mixed in, but since mot­to is already ini­tialised, what­ev­er hap­pens here doesn’t real­ly affect our result
  3. the val­ue of mot­to is there­fore nullyang

ps. please DO NOT try this at work!


 

So, that’s every­thing I have learnt about traits in the last week, hope you have found it use­ful in your path to learn Scala.

Until next time, ciao!

 

Links