62
Solid Type System vs Runtime Checks and Unit Tests Vladimir Pavkin

Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

  • Upload
    others

  • View
    6

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Solid Type Systemvs

Runtime Checks and Unit TestsVladimir Pavkin

Page 2: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

PlanFail Fast conceptType Safe Patterns

Page 3: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Fail Fast

Page 4: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Immediate andvisible failure

Page 5: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Where can it fail?

Handled runtime exceptions & assertionsUnhandled runtime failure

Page 6: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Handling runtime exceptions

assert(!list.isEmpty, "List must be empty")

try {  str.toInt} catch {  case _:Throwable => 0}

Page 7: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Where can it fail?

Runtime checksHandled runtime exceptions & assertionsUnhandled runtime failure

Page 8: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Runtime checks

if(container == null)

if(container.isInstanceOf[ContainerA])

Page 9: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Where can it fail?Unit testsRuntime checksHandled runtime exceptions & assertionsUnhandled runtime failure

Page 10: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Unit tests

it should "throw NoSuchElementException for empty stack" in {  val emptyStack = new Stack[Int]  a [NoSuchElementException] should be thrownBy {    emptyStack.pop()  }}

it should "not throw for empty stack" in {  val stackWrapper = StackWrapper(new Stack[Int])  noException should be thrownBy stackWrapper.pop()}

Page 11: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Where can it fail?LintersUnit testsRuntime checksHandled runtime exceptions & assertionsUnhandled runtime failure

Page 12: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Linters

scalacOptions ++= Seq(  "­Xlint",  "­deprecation",  "­Xfatal­warnings")

// Wrong number of args to format()logger.error(  "Failed to open %s. Error: %d"  .format(file))

Page 13: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Where can it fail?CompilerLintersUnit testsRuntime checksHandled runtime exceptions & assertionsUnhandled runtime failure

Page 14: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

The goalTo move as much as possible to

the Compiler

Page 15: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

How?Just give it

enough type information.Type system to the rescue!

Page 16: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Before we start...

Examplesdomain?

Page 17: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Beefcakes!

No offense intended :)

Page 18: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Ok?

def becomeAMan(douchebag: Person): Man =  if(douchebag.weight > 70)    new Man(douchebag.renameTo("Arny"))  else    null

No! Unhandled runtime failure!

becomeAMan(vpavkin).name  //vpavkin.weight < 70

Page 19: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

NULL

Page 20: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Can we handle this?

var man = becomeAMan(person)if(man != null)  nameelse  //...

Page 21: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Still not nice.code client has to clutter code with runtime checks (or fail)compiler won't complain if you forget to check

Page 22: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

If you control the source code,don't ever use null as a return result.

It's like farting in an elevator.

Some random guy at a random Scala forum

Page 23: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

The problem is

insufficient type information!Return type should be something like

ManOrNull

Page 24: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Option

Page 25: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Option

sealed trait Option[T]case class Some[T](x: T) extends Option[T]case object None extends Option[Nothing]

Page 26: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Better API

def becomeAMan(douchebag: Person): Option[Man] =  if(douchebag.weight > 70)    Some(new Man(douchebag.renameTo("Arny")))  else    None

code is documentationclient has to deal with None result at compile time.

Page 27: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Use wrapped value?

def firstWorkout(douchebag: Person): Option[WorkoutResult] =  becomeAMan(douchebag).map(man => man.workout())

Unwrap?

def willHaveASexyGirlfriend(douchebag: Person): Boolean =  becomeAMan(douchebag) match {    case Some(man) => true    case None => false  }

Page 28: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Exceptions

Page 29: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Classic

def workout(man: Man): WorkoutResult =  if(!man.hasShaker)    throw new Error("Not enough protein!!!!111")  else    // do some squats or stare in the mirror for 1h

Again!Client either uses try/catch or fails at runtime!Return type doesn't tell anything about possible failure

Page 30: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Let's add sometypes!

Page 31: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

scala.Eitheror

scalaz.\/

Page 32: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Declare possible failure

Page 33: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Better API

def workout(man:Man): ProteinFail \/ WorkoutResult =  if(!man.hasShaker)    ProteinFail("Not enough protein!!!!111").left  else    someWorkoutResult.right

code is documentationclient has to deal with errors at compile time.

Page 34: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

scalaz.\/

sealed trait \/[E, R]case class ­\/[E](a: E) extends (E \/ Nothing)case class \/­[R](a: R) extends (Nothing \/ R)

Page 35: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Use wrapped value?

workout(man).map(result => submitToFacebook(result))// type is// ProteinFail \/ Future[List[FacebookLike]]

Unwrap?

def tellAboutTheWorkout(w: ProteinFail \/ WorkoutResult): String =  w match {    case ­\/(fail) => "F**k your proteins, I can do without it"    case \/­(result) =>      s"Dude, eat proteins, or you won't do like me: $result"  }

Page 36: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

isInstanceOf[Man]

Page 37: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

isInstanceOf[T]

trait GymClientcase class Man(name: String) extends GymClientcase class Douchebag(name: String) extends GymClient

def gymPrice(h: GymClient): Int =  if(h.isInstanceOf[Man]){    val man = h.asInstanceOf[Man]    if(man.name == "Arny") 0 else 100  } else {    200  }

Page 38: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

So runtime.

// Add another client typecase class PrettyGirl(name:String) extends GymClient

It still compiles.

And we charge girls as much asdouchebags!

It's an unhandled runtime failure!

Page 39: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

isInstanceOf[T]

trait GymClientcase class Man(name: String) extends GymClientcase class Douchebag(name: String) extends GymClientcase class PrettyGirl(name:String) extends GymClient

def gymPrice(h: GymClient): Int =  if(h.isInstanceOf[Man]){    val man = h.asInstanceOf[Man]    if(man.name == "Arny") 0 else 100  } else {    200  }

Page 40: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

sealed ADT+

pattern matching

Page 41: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

sealed = can't be extended inother files

Page 42: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Algebraic Data Type

1) Product types

2) Sum types

Page 43: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Compiler knowsall the possible class/trait

children.

Page 44: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Sealed ADT + pattern matching

sealed trait GymClientcase class Man(name: String) extends GymClientcase class Douchebag(name: String) extends GymClient

def gymPrice(h: GymClient): Int = h match {  case Man("Arny") => 0  case _: Man => 100  case _: Douchebag => 200}// compiler checks, that match is exhaustive

Page 45: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

What if we add girls now?

sealed trait GymClientcase class Man(name: String) extends GymClientcase class Douchebag(name: String) extends GymClientcase class PrettyGirl(name:String) extends GymClient

def gymPrice(h: GymClient): Int = h match {  case Man("Arny") => 0  case _: Man => 100  case _: Douchebag => 200}// COMPILE ERROR! Match fails for PrettyGirl.

Page 46: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Compiler saved us again!

Page 47: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Tagging

Page 48: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Gym DB

case class Beefcake(id: String,                    name: String)case class GymPass(id: String,                   ownerId: String)

Page 49: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Safer: Tags

trait JustTagdef onlyTagged(value: String @@ JustTag): String  = s"Tagged string: $value"  // can use as plain String

onlyTagged("plain string") // Compiler errorval tagged = tag[JustTag]("tagged")onlyTagged(tagged) // OK

Page 50: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Gym DB: safer keys

case class Beefcake(id: String @@ Beefcake,                    name: String)case class GymPass(id: String @@ GymPass,                   ownerId: String @@ Beefcake)

Page 51: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Phantom Types

Page 52: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

PullUp

sealed trait PullUpStatefinal class Up extends PullUpStatefinal class Down extends PullUpState

Page 53: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

PullUp

class Beefcake[S <: PullUpState] private () {  def pullUp[T >: S <: Down]() =      this.asInstanceOf[Beefcake[Up]]

  def pullDown[T >: S <: Up]() =      this.asInstanceOf[Beefcake[Down]]}

object Beefcake {  def create() = new Beefcake[Down]}

Page 54: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

PullUp

val fresh = Beefcake.create() //Beefcake[Down]val heDidIt = fresh.pullUp() //Beefcake[Up]val notAgainPlease = heDidIt.pullUp()// CompileError:// inferred type arguments [Up] do not conform// to method pullUp's type parameter bounds

Page 55: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Path Dependent Types

Page 56: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

The Two Gyms

class Gym(val name: String)class Beefcake(val gym: Gym){  def talkTo(other: Beefcake): Unit =    println("Wazzup, Hetch!")}

val normalGym = new Gym("nicefitness")val swagGym = new Gym("kimberly")val normalGuy = new Beefcake(normalGym)val swagGuy = new Beefcake(swagGym)normalGuy.talkTo(swagGuy) // we don't want that

Page 57: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

The Two Gyms

Runtime solution

class Beefcake(val gym: Gym){  def talkTo(other: Beefcake): Unit = {    // throws IllegalArgumentException if false    require(this.gym == other.gym)    println("Wazzup, Hetch!")  }}

Page 58: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Path Dependent Types

class A {  class B}val a1 = new Aval a2 = new Avar b = new a1.B // type is a1.Bb = new a2.B // Compile Error: types don't match

Type depends on the value it belongs to.

Page 59: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Type safe solution

class Gym(val name: String){  class Beefcake(val gym: Gym){    def talkTo(other: Beefcake): Unit =    println("Wazzup, Hetch!")  }}

val normalGym = new Gym("nicefitness")val swagGym = new Gym("kimberly")val normalGuy = new normalGym.Beefcake(normalGym)val swagGuy = new swagGym.Beefcake(swagGym)normalGuy.talkTo(swagGuy) // doesn't compile, Yay!

Page 60: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

This is not a talk about Scala type system.

Not covered:Trait compositionExistential typesMacrosType ClassesShapeless...

Page 61: Solid Type Systemprintln("Wazzup, Hetch!") }} Path Dependent Types class A { class B} val a1 = new A val a2 = new A var b = new a1.B // type is a1.B b = new a2.B // Compile Error:

Q & A