scala self types by gregor heine, principal software engineer at gilt

51
SCALA SELF-TYPES Gregor 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

Upload: gilt-tech-talks

Post on 15-Jan-2015

3.704 views

Category:

Technology


2 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 2: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Lets build a car...

Page 3: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Components

● Engine● Fuel Tank● Wheels● Gearbox● Steering● Accelerator● Clutch● etc...

Page 4: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

OK, let's write some interfaces

Page 5: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

trait Engine {

def start(): Unit

def stop(): Unit

def isRunning(): Boolean

def fuelType: FuelType

}

trait Car {

def drive(): Unit

def park(): Unit

}

Page 6: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Well actually, in Scala we can also add some implementation

Page 7: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

}

Page 8: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Fine, so how do we put an Engine into a Car?

Page 9: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

First attempt: Inheritance-based assembly

Page 10: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

trait Car extends Engine {

def drive() {

start()

println("Vroom vroom")

}

def park() {

if (isRunning()) println("Break!")

stop()

}

}

val myCar = new Car extends DieselEngine

Page 11: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Hmm, this isn't great: A Car is also an Engine

:(

This should really be a has-a relation, right?

Page 12: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Second attempt: Composition-based assembly

Page 13: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

}

Page 14: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Hmm OK, a Car has an Engine.

That's better.But...

Page 15: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

There is no guarantee that the Engine in myCar isn't used in

another Car!

:(

Page 16: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

If only there was a way to "mix-in" an Engine into my car rather than supplying

it from the outside

Enter: self-types

Page 17: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

“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

Page 18: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Erm, what, what, what?

Fine, let's look at an example:

Page 19: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

trait Car {

this: Engine => // self-type

def drive() {

start()

println("Vroom vroom")

}

def park() {

println("Break!")

stop()

}

}

object MyCar extends Car with DieselEngine

Page 20: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 21: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

So what's happening here?

Page 22: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

● 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”

Page 23: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

So, tell me more about self-types

Page 24: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

No need to call the self-type "this"

You can use this-aliasing to give it a different name:

Page 25: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 26: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

// ...

}

Page 27: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

A self-type can require multiple types:

trait Car {

this: Engine with FuelTank with GearBox =>

// ...

}

Used when the trait has multiple dependencies

Page 28: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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)

Page 29: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

So all is good with self-types?

Page 30: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Hmm, well no:

val myCar = new Car extends DieselEngine {}

myCar is still a Car and an Engine

:(

Page 31: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 32: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

What we need is a way to wire up and assemble our components without changing their identity

Page 33: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Enter: The Cake Pattern

Page 34: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

http://patternandco.com/wp-content/uploads/cake21b.jpg

Page 35: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Ok, so here's the recipe:

Page 36: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 37: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

}

}

Page 38: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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() { … }

}

}

Page 39: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Great, now let's tie it all together:(remember a Car has a couple more components beside an Engine)

Page 40: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 41: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

If we want to write a CarTest, we can provide mock instances for the components we're not testing:

Page 42: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 43: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Time to recap

Page 44: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 45: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 46: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 47: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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

Page 48: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

http://4.bp.blogspot.com/-OHTIQo-k2_k/Tmjkj66eIYI/AAAAAAAACfY/n1Vj1fseVQ0/s1600/Boom.jpg

Page 49: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Ergo:

Be very careful exposing self-types as part of a library

Page 50: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

Fin.

Page 51: Scala Self Types by Gregor Heine, Principal Software Engineer at Gilt

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