scala self types by gregor heine, principal software engineer at gilt
DESCRIPTION
TRANSCRIPT
SCALA SELF-TYPESGregor Heine, Gilt Groupe
http://image.motortrend.com/f/features/consumer/1301_chevrolet_corvette_60_years_american_icon_part_1/42023018/1961-Chevrolet-Corvette-front.jpg
Lets build a car...
Components
● Engine● Fuel Tank● Wheels● Gearbox● Steering● Accelerator● Clutch● etc...
OK, let's write some interfaces
trait Engine {
def start(): Unit
def stop(): Unit
def isRunning(): Boolean
def fuelType: FuelType
}
trait Car {
def drive(): Unit
def park(): Unit
}
Well actually, in Scala we can also add some implementation
trait Engine {
private var running = false
def start(): Unit = {
if (!running) println("Engine started")
running = true
}
def stop(): Unit = {
if (running) println("Engine stopped")
running = false
}
def isRunning(): Boolean = running
def fuelType: FuelType
}
trait DieselEngine extends Engine {
override val fuelType = FuelType.Diesel
}
Fine, so how do we put an Engine into a Car?
First attempt: Inheritance-based assembly
trait Car extends Engine {
def drive() {
start()
println("Vroom vroom")
}
def park() {
if (isRunning()) println("Break!")
stop()
}
}
val myCar = new Car extends DieselEngine
Hmm, this isn't great: A Car is also an Engine
:(
This should really be a has-a relation, right?
Second attempt: Composition-based assembly
trait Car {
def engine: Engine
def drive() {
engine.start()
println("Vroom vroom")
}
def park() {
if (engine.isRunning() ) println("Break!")
engine.stop()
}
}
val myCar = new Car {
override val engine = new DieselEngine()
}
Hmm OK, a Car has an Engine.
That's better.But...
There is no guarantee that the Engine in myCar isn't used in
another Car!
:(
If only there was a way to "mix-in" an Engine into my car rather than supplying
it from the outside
Enter: self-types
“A self type of a trait is the assumed type of this, the receiver, to be used within the trait. Any concrete class that mixes in the trait must ensure that its type conforms to the trait’s self type.”
Programming in Scala
Erm, what, what, what?
Fine, let's look at an example:
trait Car {
this: Engine => // self-type
def drive() {
start()
println("Vroom vroom")
}
def park() {
println("Break!")
stop()
}
}
object MyCar extends Car with DieselEngine
trait Car {
this: Engine => // self-type
def drive() {
start()
println("Vroom vroom")
}
def park() {
println("Break!")
stop()
}
}
object MyCar extends Car with DieselEngine
Looks a bit like composition-based assembly
Looks like inheritance-based assembly
So what's happening here?
● Self-types are used with traits
● Explicitly declare the type of the value this
● Specify the requirements on any concrete class or instance the trait is mixed into.
● Declare a dependency of the trait on another type: “In order to use me you have to be one of those”
So, tell me more about self-types
No need to call the self-type "this"
You can use this-aliasing to give it a different name:
trait Car {
engine: Engine =>
def drive() {
engine.start()
println("Vroom vroom")
}
}
Useful for nested classes or traits, where accessing a particular this would otherwise be difficult
Self-types don’t automatically inherit:
trait HondaCar extends Car
// error: self-type HondaCar does not conform to Car's selftype Car with Engine
Need to repeat the self-type in subtypes:
trait HondaCar extends Car {
this: Engine =>
// ...
}
A self-type can require multiple types:
trait Car {
this: Engine with FuelTank with GearBox =>
// ...
}
Used when the trait has multiple dependencies
The self-type can be a structural type:
trait Car {
this: {
def start: Unit
def stop: Unit
} =>
// ...
}
Allows for safe mixins with duck-typing.(Useful when interacting with external dependencies)
So all is good with self-types?
Hmm, well no:
val myCar = new Car extends DieselEngine {}
myCar is still a Car and an Engine
:(
So here's a quick fix:
val myCar: Car = new Car extends DieselEngine {}
… but that's cheating (a bit)! And it doesn't work for singletons:
object MyCar extends Car with DieselEngine
What we need is a way to wire up and assemble our components without changing their identity
Enter: The Cake Pattern
http://patternandco.com/wp-content/uploads/cake21b.jpg
Ok, so here's the recipe:
For each component in our system, supply a Component trait, that declares:
● Any dependent components, using self-types● A trait describing the component's interface● An abstract val that will be instantiated with
an instance of the component● Optionally, implementations of the
component interface
trait EngineComponent {
trait Engine {
private var running = false
def start(): Unit = { /* as before */ }
def stop(): Unit = {/* as before */ }
def isRunning: Boolean = running
def fuelType: FuelType
}
protected val engine: Engine
protected class DieselEngine extends Engine {
override val fuelType = FuelType.Diesel
}
}
trait CarComponent {
this: EngineComponent => // gives access to engine
trait Car {
def drive(): Unit
def park(): Unit
}
protected val car: Car
protected class HondaCar extends Car {
override def drive() {
engine.start()
println("Vroom vroom")
}
override def park() { … }
}
}
Great, now let's tie it all together:(remember a Car has a couple more components beside an Engine)
object App extends CarComponent with EngineComponent with FuelTankComponent with GearboxComponent {
override protected val engine = new DieselEngine()
override protected val fuelTank = new FuelTank(capacity = 60)
override protected val gearBox = new FiveGearBox()
override val car = new HondaCar()
}
MyApp.car.drive()
MyApp.car.park()
If we want to write a CarTest, we can provide mock instances for the components we're not testing:
val testCar = new CarComponent with EngineComponent with FuelTankComponent with GearboxComponent {
override protected val engine = mock[Engine] // mock engine
override protected val fuelTank = mock[FuelTank] // mock tank
override protected val gearBox = new FiveGearBox() // an actual gearbox
override val car = new HondaCar()
}.car
Time to recap
The Cake Pattern
+ Environment specific assembly of components+ Compile-time checked, typesafe+ Everything is immutable+ No external DI descriptor
- Can become hard to read and understand- May be difficult to configure components- No control over initialization order- Self-types prone to fragile base class problem
Remember this:
trait Car {
engine: Engine =>
def drive() { /* start engine */ }
def park() { /* stop engine */ }
}
object MyCar extends Car with DieselEngine
How did they do it?
Let's decompile MyCar.class
> javap com.example.MyCar
Compiled from "Car.scala"
public final class com.example.MyCar extends java.lang.Object{
public static void park();
public static void drive();
public static boolean isRunning();
public static void stop();
public static void start();
/* … */
}
All functions from Car and Engine have been "lifted" into the top-level class!
Imagine Car and Engine are part of a library that I'm using to construct MyCar.
Any change to library-private interactions between these traits are not reflected in MyCar.class
http://4.bp.blogspot.com/-OHTIQo-k2_k/Tmjkj66eIYI/AAAAAAAACfY/n1Vj1fseVQ0/s1600/Boom.jpg
Ergo:
Be very careful exposing self-types as part of a library
Fin.
Sources:● M. Odersky, L. Spoon, B. Venners: Programming in Scala, 2nd Edition● C. S. Horstmann: Scala for the Impatient● M. Odersky: Scalable Component Abstractions
http://lampwww.epfl.ch/~odersky/papers/ScalableComponent.pdf● J. Bonér: Real-World Scala: Dependency Injection (DI)
http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di● A Tour of Scala: Explicitly Typed Self References
http://www.scala-lang.org/old/node/124● Cake pattern in depth
http://www.cakesolutions.net/teamblogs/2011/12/19/cake-pattern-in-depth● Dependency Injection In Scala using Self Type Annotations
http://blog.knoldus.com/2013/01/21/dependency-injection-in-scala-using-self-type-annotations● Cake Pattern in Scala / Self type annotations / Explicitly Typed Self References - explained
https://coderwall.com/p/t_rapw● DI in Scala: Cake Pattern pros & cons
http://www.warski.org/blog/2011/04/di-in-scala-cake-pattern-pros-cons