solid type system
play

Solid Type System vs Runtime Checks and Unit Tests Vladimir Pavkin - PowerPoint PPT Presentation

Solid Type System vs Runtime Checks and Unit Tests Vladimir Pavkin Plan Fail Fast concept Type Safe Patterns Fail Fast Immediate and visible failure Where can it fail? Handled runtime exceptions & assertions Unhandled runtime failure


  1. Solid Type System vs Runtime Checks and Unit Tests Vladimir Pavkin

  2. Plan Fail Fast concept Type Safe Patterns

  3. Fail Fast

  4. Immediate and visible failure

  5. Where can it fail? Handled runtime exceptions & assertions Unhandled runtime failure

  6. Handling runtime exceptions assert(!list.isEmpty, "List must be empty") try { str.toInt } catch { case _:Throwable => 0 }

  7. Where can it fail? Runtime checks Handled runtime exceptions & assertions Unhandled runtime failure

  8. Runtime checks if(container == null) if(container.isInstanceOf[ContainerA])

  9. Where can it fail? Unit tests Runtime checks Handled runtime exceptions & assertions Unhandled runtime failure

  10. 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() }

  11. Where can it fail? Linters Unit tests Runtime checks Handled runtime exceptions & assertions Unhandled runtime failure

  12. Linters scalacOptions ++= Seq( "­Xlint", "­deprecation", "­Xfatal­warnings" ) // Wrong number of args to format() logger.error( "Failed to open %s. Error: %d" .format(file) )

  13. Where can it fail? Compiler Linters Unit tests Runtime checks Handled runtime exceptions & assertions Unhandled runtime failure

  14. The goal To move as much as possible to the Compiler

  15. How? Just give it enough type information. Type system to the rescue!

  16. Before we start... Examples domain?

  17. Beefcakes! No offense intended :)

  18. 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

  19. NULL

  20. Can we handle this? var man = becomeAMan(person) if(man != null) name else //...

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

  22. 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

  23. The problem is insufficient type information! Return type should be something like ManOrNull

  24. Option

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

  26. Better API def becomeAMan(douchebag: Person): Option[Man] = if(douchebag.weight > 70) Some(new Man(douchebag.renameTo("Arny"))) else None code is documentation client has to deal with None result at compile time.

  27. 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 }

  28. Exceptions

  29. 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

  30. Let's add some types!

  31. scala.Either or scalaz.\/

  32. Declare possible failure

  33. Better API def workout(man:Man): ProteinFail \/ WorkoutResult = if(!man.hasShaker) ProteinFail("Not enough protein!!!!111").left else someWorkoutResult.right code is documentation client has to deal with errors at compile time.

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

  35. 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" }

  36. isInstanceOf[Man]

  37. isInstanceOf[T] trait GymClient case class Man(name: String) extends GymClient case 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 }

  38. So runtime. // Add another client type case class PrettyGirl(name:String) extends GymClient It still compiles. And we charge girls as much as douchebags! It's an unhandled runtime failure!

  39. isInstanceOf[T] trait GymClient case class Man(name: String) extends GymClient case class Douchebag(name: String) extends GymClient case 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 }

  40. sealed ADT + pattern matching

  41. sealed = can't be extended in other files

  42. Algebraic Data Type 1) Product types 2) Sum types

  43. Compiler knows all the possible class/trait children.

  44. Sealed ADT + pattern matching sealed trait GymClient case class Man(name: String) extends GymClient case 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

  45. What if we add girls now? sealed trait GymClient case class Man(name: String) extends GymClient case class Douchebag(name: String) extends GymClient case 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.

  46. Compiler saved us again!

  47. Tagging

  48. Gym DB case class Beefcake(id: String, name: String) case class GymPass(id: String, ownerId: String)

  49. Safer: Tags trait JustTag def onlyTagged(value: String @@ JustTag): String = s"Tagged string: $value" // can use as plain String onlyTagged("plain string") // Compiler error val tagged = tag[JustTag]("tagged") onlyTagged(tagged) // OK

  50. Gym DB: safer keys case class Beefcake(id: String @@ Beefcake, name: String) case class GymPass(id: String @@ GymPass, ownerId: String @@ Beefcake)

  51. Phantom Types

  52. PullUp sealed trait PullUpState final class Up extends PullUpState final class Down extends PullUpState

  53. 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] }

  54. 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

  55. Path Dependent Types

  56. 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

  57. 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!") } }

  58. 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: types don't match Type depends on the value it belongs to.

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

  60. This is not a talk about Scala type system. Not covered: Trait composition Existential types Macros Type Classes Shapeless ...

  61. Q & A

  62. Thank you! goo.gl/U0WYAB PDF

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend