Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Type Your Business @ ScalaIO, October 2018

Type Your Business @ ScalaIO, October 2018

This talk was presented at ScalaIO in Lyon, France

Omer van Kloeten

October 31, 2018
Tweet

Other Decks in Technology

Transcript

  1. § To err is human § Typos § Bugs §

    Regressions @omervk 2 WORKS ON MY MACHINE
  2. § More Code to Write § And Review § And

    Maintain § So much code @omervk 5
  3. § More Code to Write § And Review § And

    Maintain § So much code § Exhaustive? § 100% coverage is a lie @omervk 6
  4. § More Code to Write § And Review § And

    Maintain § So much code § Exhaustive? § 100% coverage is a lie § Tests are poor documentation @omervk 7
  5. § Documentation /** * Connects a cat to all vet

    clinics * @param catId the id of the cat * @return the number of clinics connected */ @omervk 8
  6. § Documentation § Code /** * Connects a cat to

    all vet clinics * @param catId the id of the cat * @return the number of clinics connected */ def connect(catId: Int, clinicId: Int): Boolean @omervk 9
  7. § Documentation § Code § Tests /** * Connects a

    cat to all vet clinics * @param catId the id of the cat * @return the number of clinics connected */ def connect(catId: Int, clinicId: Int): Boolean test("Cat may not be associated with more than one clinic") @omervk 10
  8. § Documentation § Code § Tests § Exceptions?! § And

    this is a good specimen! /** * Connects a cat to all vet clinics * @param catId the id of the cat * @return the number of clinics connected */ def connect(catId: Int, clinicId: Int): Boolean test("Cat may not be associated with more than one clinic") @omervk 11
  9. § We had compile-time annotations § Enforce invariant business rules

    § Replace the basic, repetitive validation tests and docs @omervk 12
  10. § Impact our code’s users § Discover failures early §

    Understand where validations happen @omervk 13
  11. /** * @param host the host, denoted as an IPv4

    number, defined in * RFC-791 (https://tools.ietf.org/html/rfc791) * @param port the port, a 16-bit unsigned integer * @throws IllegalArgumentException host is not valid * @throws IllegalArgumentException port is not valid */ final case class Connection(host: String, port: Int) @omervk 25
  12. type Port = Int Refined Interval.Closed[W.`0`.T, W.`65535`.T] final case class

    Connection(host: String Refined IPv4, port: Port) @omervk 27
  13. type Port = Int Refined Interval.Closed[W.`0`.T, W.`65535`.T] final case class

    Connection(host: String Refined IPv4, port: Port) Connection("not a host", -1) // Fails to compile Connection("127.0.0.1", 8080) // Compiles fine @omervk 28
  14. val port: Either[String, Port] = refineV(intMaybePort) § Know what failed

    § Know why it failed § Proactive, not reactive @omervk 29
  15. § fthomas/refined § Type-level combinable predicates § List[Int] Refined (NonEmpty

    And Forall[Positive]) § Not, And, Or, Xor, Nand, Nor, AllOf, AnyOf, OneOf § Ecosystem @omervk 30
  16. § Generic: Equal § Boolean: True, False § Char: Digit,

    Letter, LetterOrDigit, LowerCase, UpperCase, Whitespace § Numeric: Less, LessEqual, Greater, GreaterEqual, Positive, NonPositive, Negative, NonNegative, Interval, Modulo, Divisible, NonDivisible, Even, Odd § String: EndsWith, IPv4, IPv6, MatchesRegex, Regex, StartsWith, Uri, Url, Uuid, ValidByte, ValidShort, ValidInt, ValidLong, ValidFloat, ValidDouble, ValidBigInt, ValidBigDecimal, Xml, XPath, Trimmed, HexStringSpec § Collection: Contains, Count, Empty, NonEmpty, Forall, Exists, Head, Index, Init, Last, Tail, Size, MinSize, MaxSize @omervk 31
  17. § Run-time exceptions à Compile-time errors § Obligate callers explicitly

    § Document preconditions § Less code § Automated using compiler & macros @omervk 32
  18. class Dog { class WaggingTail { } } def pet(dog:

    Dog) = { new Dog.WaggingTail } @omervk 37
  19. class Dog { class WaggingTail { } } def pet(dog:

    Dog): dog.WaggingTail = { new dog.WaggingTail } dog == pet(dog).dog // Guaranteed! @omervk 38
  20. § IO Barriers § Network § Database § File system

    § OS § Solution: § Use better abstractions (libraries) § Integration Tests @omervk 40
  21. Types Validations § “port is a valid port number” §

    “the tail we got was the one of the dog we petted” Tests Complex Business Rules § “when a dog has already been petted by another user, wag the tail twice as hard” @omervk 41
  22. final case class DogId(id: Int) extends AnyVal final case class

    Dog(id: DogId, /*...*/) extends Animal def petDog(id: DogId): Unit petDog(cat.id) @omervk 45
  23. implicit def extractId(id: DogId): Int = id.id implicit def extractId(id:

    ParrotId): Int = id.id implicit def extractId(id: CapybaraId): Int = id.id implicit def extractId(id: CatId): Int = id.id implicit def extractId(id: ScalazId): Int = id.id Ugh. @omervk 50
  24. CatId(10) So we change to: final case class CatId private

    (id: Int) extends AnyVal @omervk 51
  25. sealed abstract case class CatId (id: Int) extends AnyVal object

    CatId { private[persistence] def apply(id: Int): CatId = new CatId(id) {} } @omervk 54
  26. sealed abstract case class CatId (id: Int) extends AnyVal object

    CatId { private[persistence] def apply(id: Int): CatId = { // Business logic… new CatId(id) {} } } @omervk 55
  27. sealed abstract case class CatId (id: Int) extends AnyVal object

    CatId { private[persistence] def apply(id: Int): CatId = { // Business logic… new CatId(id) {} } } @omervk Illegal inheritance from value class CatId 56
  28. trait DogId val id: Int with DogId = 10.asInstanceOf[Int with

    DogId] def query(id: Int with DogId): Dog @omervk 57
  29. § Int with DogId is an Int § Zero runtime

    overhead § Accidents less likely @omervk 58
  30. § Convention: Int @@ DogId § Don’t roll your own:

    § shapeless § Scalaz § etc. @omervk 59
  31. type DogId = DogId.Type object DogId { type Repr =

    Int type Base = Any { type DogId$newtype } trait Tag extends Any type Type <: Base with Tag def apply(id: Int): DogId = id.asInstanceOf[DogId] implicit final class Ops$newtype(val $this$: Type) extends AnyVal { def id: Int = $this$.asInstanceOf[Int] } } @omervk 61
  32. § Macro § Looks like a case class § Actually

    tags § Zero-cost wrapper § estatico/scala-newtype § Opaque Types (SIP-35) @newtype case class DogId(id: Int) @omervk 62
  33. @newsubtype class DogId(id: Int) object DogId { private[persistence] def apply(id:

    Int): DogId = { // Business logic here... id.asInstanceOf[DogId] } } @omervk 65
  34. @newsubtype class DogId(id: Int) object DogId { private[persistence] def apply(id:

    Int): DogId = { // Business logic here... id.asInstanceOf[DogId] } } val dogId: DogId = DogId(10) val unwrapped: Int = dogId @omervk 66
  35. final case class DogWithId(id: DogId, entity: Dog) def query(id: DogId):

    DogWithId def update(entity: DogWithId): DogWithId def create(entity: Dog): DogWithId @omervk 70
  36. final case class With[Entity, Id](id: Id, entity: Entity) def query(id:

    Id): With[Entity, Id] def update(entity: With[Entity, Id]): With[Entity, Id] def create(entity: Entity): With[Entity, Id] @omervk 71
  37. final case class With[Entity, Id](id: Id, entity: Entity) def query(id:

    Id): Entity With Id def update(entity: Entity With Id): Entity With Id def create(entity: Entity): Entity With Id @omervk 72
  38. final case class With[Entity, Id](id: Id, entity: Entity) abstract class

    Repository[Entity, Id] { def query(id: Id): Entity With Id def update(entity: Entity With Id): Entity With Id def create(entity: Entity): Entity With Id } More: https://www.iravid.com/posts/slick-and-shapeless.html @omervk 73
  39. final case class With[Entity, Id](id: Id, entity: Entity) abstract class

    Repository[Entity, Id] { def query(id: Id): Entity With Id def update(entity: Entity With Id): Entity With Id def create(entity: Entity): Entity With Id } @omervk 75
  40. final case class With[Entity, Id](id: Id, entity: Entity) abstract class

    Repository[Entity, Id] { def query(id: Id): Entity With Id def update(entity: Entity With Id): Entity With Id def create(entity: Entity): Entity With Id } trait Represents[Identifier, Entity] @omervk 76
  41. final case class With[Entity, Id](id: Id, entity: Entity) (implicit ev:

    Id Represents Entity) abstract class Repository[Entity, Id](implicit ev: Id Represents Entity) { def query(id: Id): Entity With Id def update(entity: Entity With Id): Entity With Id def create(entity: Entity): Entity With Id } trait Represents[Identifier, Entity] @omervk 77
  42. implicit val idOfDog: DogId Represents Dog = ... § Won’t

    compile: § new Repository[Dog, CatId] {} § new Repository[Cat, DogId] {} § new Repository[Dog, DogId] {}.query(catId) @omervk 78
  43. @newsubtype class DogId(a: Int) object DogId { private[persistence] def apply(a:

    Int): DogId = { // Business logic here... a.asInstanceOf[DogId] } } final case class Dog(/*...*/) object Dog { implicit val idOfDog: DogId Represents Dog = new DogId Represents Dog { } } final class DogRepository extends Repository[Dog, DogId] @omervk 79
  44. § Path-Dependent Types § Self-Recursive / F-bound Types § Product

    and Sum Types § Structural Types § Type Members § Kinds § Variance § Existential Types* @omervk 81 Photo Opportunity
  45. § -Xfatal-warnings § Linter § -Xlint § WartRemover § Scalafix

    § Your IDE § See: Power Up Your Build @omervk 82
  46. § Increase robustness § Fail-fast § Replace some tests and

    documentation § Where can your code benefit? @omervk 83