Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Introduction to type classes in 30 min

1,434 views

Published on

Introduction to type classes presented at Scalar

Published in: Software

Introduction to type classes in 30 min

  1. 1. Having a cake and eating it too. Introduction to Type classes (in Scala)
  2. 2. “The Story of a Blackout” In 4 acts
  3. 3. Act.1: “Loss & Grief”
  4. 4. 503 Service Temporarily Unavailable nginx
  5. 5. 503 Service Temporarily Unavailable nginx
  6. 6. The 5 Stages of Loss and Grief
  7. 7. The 5 Stages of Loss and Grief 1. Denial
  8. 8. The 5 Stages of Loss and Grief 1. Denial 2. Anger
  9. 9. The 5 Stages of Loss and Grief 1. Denial 2. Anger 3. Bargaining
  10. 10. The 5 Stages of Loss and Grief 1. Denial 2. Anger 3. Bargaining 4. Depression
  11. 11. The 5 Stages of Loss and Grief 1. Denial 2. Anger 3. Bargaining 4. Depression 5. Acceptance
  12. 12. Act.2: “The Mess”
  13. 13. auction
  14. 14. auctionify
  15. 15. auctionify.io
  16. 16. Domain & Feature Requests Domain: ● Users place Orders in the auction system Order
  17. 17. Domain & Feature Requests Domain: ● Users place Orders in the auction system ● Orders can be: ○ General - contain list of Products ○ Complex - combined from two or more other Orders ○ Cancelled - used to be fully fledged Orders, but now are cancelled Order General Complex Cancelled
  18. 18. Domain & Feature Requests Domain: ● Users place Orders in the auction system ● Orders can be: ○ General - contain list of Products ○ Complex - combined from two or more other Orders ○ Cancelled - used to be fully fledged Orders, but now are cancelled ● Products can be: ○ Basic - id & price ○ Discounted - wrapped Product & discount ○ OutOfStock - used to be in warehouse, but now gone (sorry) Order General Complex Cancelled Product Basic Discounter Out of Stock
  19. 19. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  20. 20. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  21. 21. sealed trait Order case class GeneralOrder(products: List[Product]) extends Order case object CancelledOrder extends Order case class ComplexOrder(orders: List[Order]) extends Order sealed trait Product case class BasicProduct(id: Int, price: BigDecimal) extends Product case class DiscountedProduct(product: Product, discount: Double) extends Product case object OutOfStock extends Product
  22. 22. sealed trait Order case class GeneralOrder(products: List[Product]) extends Order case object CancelledOrder extends Order case class ComplexOrder(orders: List[Order]) extends Order sealed trait Product case class BasicProduct(id: Int, price: BigDecimal) extends Product case class DiscountedProduct(product: Product, discount: Double) extends Product case object OutOfStock extends Product
  23. 23. sealed trait Order case class GeneralOrder(products: List[Product]) extends Order case object CancelledOrder extends Order case class ComplexOrder(orders: List[Order]) extends Order sealed trait Product case class BasicProduct(id: Int, price: BigDecimal) extends Product case class DiscountedProduct(product: Product, discount: Double) extends Product case object OutOfStock extends Product
  24. 24. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  25. 25. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  26. 26. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  27. 27. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  28. 28. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  29. 29. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  30. 30. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  31. 31. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) } [error] OrderTest.scala evaluate is not a member of jw.ComplexOrder [error] order.evaluate should equal (BigDecimal("11.0")) [error] ^ [error] one error found
  32. 32. Subtype Polymorphism
  33. 33. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) } [error] OrderTest.scala evaluate is not a member of jw.ComplexOrder [error] order.evaluate should equal (BigDecimal("11.0")) [error] ^ [error] one error found
  34. 34. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  35. 35. sealed trait Order case class GeneralOrder(products: List[Product]) extends Order case object CancelledOrder extends Order case class ComplexOrder(orders: List[Order]) extends Order sealed trait Product case class BasicProduct(id: Int, price: BigDecimal) extends Product case class DiscountedProduct(product: Product, discount: Double) extends Product case object OutOfStock extends Product
  36. 36. trait Evaluatable[T] { def evaluate: T } sealed trait Order extends Evaluatable[BigDecimal] case object CancelledOrder extends Order { def evaluate: BigDecimal = BigDecimal("0.0") } case class ComplexOrder(orders: List[Order]) extends Order { def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + o.evaluate } } case class GeneralOrder(products: List[Product]) extends Order { def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + p.evaluate } }
  37. 37. trait Evaluatable[T] { def evaluate: T } sealed trait Order extends Evaluatable[BigDecimal] case object CancelledOrder extends Order { def evaluate: BigDecimal = BigDecimal("0.0") } case class ComplexOrder(orders: List[Order]) extends Order { def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + o.evaluate } } case class GeneralOrder(products: List[Product]) extends Order { def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + p.evaluate } }
  38. 38. trait Evaluatable[T] { def evaluate: T } sealed trait Order extends Evaluatable[BigDecimal] case object CancelledOrder extends Order { def evaluate: BigDecimal = BigDecimal("0.0") } case class ComplexOrder(orders: List[Order]) extends Order { def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + o.evaluate } } case class GeneralOrder(products: List[Product]) extends Order { def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + p.evaluate } }
  39. 39. trait Evaluatable[T] { def evaluate: T } sealed trait Order extends Evaluatable[BigDecimal] case object CancelledOrder extends Order { def evaluate: BigDecimal = BigDecimal("0.0") } case class ComplexOrder(orders: List[Order]) extends Order { def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + o.evaluate } } case class GeneralOrder(products: List[Product]) extends Order { def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + p.evaluate } }
  40. 40. trait Evaluatable[T] { def evaluate: T } sealed trait Order extends Evaluatable[BigDecimal] case object CancelledOrder extends Order { def evaluate: BigDecimal = BigDecimal("0.0") } case class ComplexOrder(orders: List[Order]) extends Order { def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + o.evaluate } } case class GeneralOrder(products: List[Product]) extends Order { def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + p.evaluate } }
  41. 41. trait Evaluatable[T] { def evaluate: T } sealed trait Product extends Evaluatable[BigDecimal] case class BasicProduct(id: Int, price: BigDecimal) extends Product { def evaluate: BigDecimal = price } case class DiscountedProduct(product: Product, discount: Double) extends Product { def evaluate: BigDecimal = product.evaluate * (1 - discount) } case object OutOfStock extends Product { def evaluate: BigDecimal = BigDecimal("0.0") }
  42. 42. trait Evaluatable[T] { def evaluate: T } sealed trait Product extends Evaluatable[BigDecimal] case class BasicProduct(id: Int, price: BigDecimal) extends Product { def evaluate: BigDecimal = price } case class DiscountedProduct(product: Product, discount: Double) extends Product { def evaluate: BigDecimal = product.evaluate * (1 - discount) } case object OutOfStock extends Product { def evaluate: BigDecimal = BigDecimal("0.0") }
  43. 43. trait Evaluatable[T] { def evaluate: T } sealed trait Product extends Evaluatable[BigDecimal] case class BasicProduct(id: Int, price: BigDecimal) extends Product { def evaluate: BigDecimal = price } case class DiscountedProduct(product: Product, discount: Double) extends Product { def evaluate: BigDecimal = product.evaluate * (1 - discount) } case object OutOfStock extends Product { def evaluate: BigDecimal = BigDecimal("0.0") }
  44. 44. trait Evaluatable[T] { def evaluate: T } sealed trait Product extends Evaluatable[BigDecimal] case class BasicProduct(id: Int, price: BigDecimal) extends Product { def evaluate: BigDecimal = price } case class DiscountedProduct(product: Product, discount: Double) extends Product { def evaluate: BigDecimal = product.evaluate * (1 - discount) } case object OutOfStock extends Product { def evaluate: BigDecimal = BigDecimal("0.0") }
  45. 45. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) }
  46. 46. test("should evaluate order") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = GeneralOrder(DiscountedProduct( product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o3 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: o3 :: Nil) order.evaluate should equal (BigDecimal("11.0")) } [info] OrderTest: [info] - should evaluate order
  47. 47. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  48. 48. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  49. 49. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  50. 50. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) }
  51. 51. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) }
  52. 52. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) }
  53. 53. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) }
  54. 54. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) }
  55. 55. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) }
  56. 56. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) }
  57. 57. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) } [error] OrderTest.scala average is not a member of jw.Order [error] Order.average should equal (BigDecimal("5.0")) [error] ^ [error] one error found
  58. 58. object Stat { def mean(xs: Seq[BigDecimal]): BigDecimal = ??? }
  59. 59. object Stat { def mean(xs: Seq[BigDecimal]): BigDecimal = xs.reduce(_ + _) / xs.size }
  60. 60. object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) } object Stat { def mean(xs: Seq[BigDecimal]): BigDecimal = xs.reduce(_ + _) / xs.size }
  61. 61. AWESOME! I just need it to work for Doubles and Ints as well! Can you make it more generic? Thx!
  62. 62. object Stat { def mean(xs: Seq[BigDecimal]): BigDecimal = xs.reduce(_ + _) / xs.size }
  63. 63. object Stat { def mean(xs: Seq[Number]): Number = xs.reduce(_ + _) / xs.size }
  64. 64. object Stat { def mean(xs: Seq[Number]): Number = xs.reduce(_ + _) / xs.size }
  65. 65. trait Number[A] { def value: A def +(other: Number[A]): Number[A] def /(other: Number[A]): Number[A] def /(other: Int): Number[A] }
  66. 66. trait Number[A] { def value: A def +(other: Number[A]): Number[A] def /(other: Number[A]): Number[A] def /(other: Int): Number[A] } case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] { def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value) def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value) def /(other: Int) = BigDecimalNumber(value / other) }
  67. 67. trait Number[A] { def value: A def +(other: Number[A]): Number[A] def /(other: Number[A]): Number[A] def /(other: Int): Number[A] } case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] { def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value) def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value) def /(other: Int) = BigDecimalNumber(value / other) }
  68. 68. trait Number[A] { def value: A def +(other: Number[A]): Number[A] def /(other: Number[A]): Number[A] def /(other: Int): Number[A] } case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] { def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value) def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value) def /(other: Int) = BigDecimalNumber(value / other) } case class IntNumber(value: Int) extends Number[Int] { def +(other: Number[Int]) = IntNumber(value + other.value) def /(other: Number[Int]) = IntNumber(value / other.value) def /(other: Int) = IntNumber(value / other) }
  69. 69. trait Number[A] { def value: A def +(other: Number[A]): Number[A] def /(other: Number[A]): Number[A] def /(other: Int): Number[A] } case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] { def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value) def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value) def /(other: Int) = BigDecimalNumber(value / other) } case class IntNumber(value: Int) extends Number[Int] { def +(other: Number[Int]) = IntNumber(value + other.value) def /(other: Number[Int]) = IntNumber(value / other.value) def /(other: Int) = IntNumber(value / other) } case class DoubleNumber(value: Double) extends Number[Double] { def +(other: Number[Double]) = DoubleNumber(value + other.value) def /(other: Number[Double]) = DoubleNumber(value / other.value) def /(other: Int) = DoubleNumber(value / other) }
  70. 70. object Stat { def mean(xs: Seq[BigDecimal]): BigDecimal = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  71. 71. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  72. 72. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  73. 73. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  74. 74. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  75. 75. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  76. 76. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))) }
  77. 77. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))) }
  78. 78. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))) }
  79. 79. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value }
  80. 80. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value }
  81. 81. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value }
  82. 82. test("should calculate average") { val o1 = GeneralOrder(DiscountedProduct(product = BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil) val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil) val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0")) } [info] OrderTest: [info] - should evaluate order [info] - should calculate average
  83. 83. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  84. 84. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  85. 85. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  86. 86. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) }
  87. 87. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) }
  88. 88. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) }
  89. 89. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) }
  90. 90. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) }
  91. 91. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) }
  92. 92. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) } [error] OrderTest.scala not found: value JsonSerializer [error] JsonSerializer.write(order) should equal(expectedJson) [error] ^
  93. 93. object JsonSerializer { def write(order: Order): String = ... }
  94. 94. I have few objects I also need to serialize to JSON. Can you make your code a bit more generic? Thanks!!!
  95. 95. object JsonSerializer { def write(order: Order): String = ... }
  96. 96. object JsonSerializer { def write(sth: ???): String = ... }
  97. 97. object JsonSerializer { def write(serializable: JsonSerializable) = ... }
  98. 98. object JsonSerializer { def write(serializable: JsonSerializable) = ... } trait JsonSerializable { def toJson: JsonValue }
  99. 99. object JsonSerializer { def write(serializable: JsonSerializable) = JsonWriter.write(serializable.toJson) } trait JsonSerializable { def toJson: JsonValue }
  100. 100. object JsonSerializer { def write(serializable: JsonSerializable) = JsonWriter.write(serializable.toJson) } trait JsonSerializable { def toJson: JsonValue }
  101. 101. sealed trait JsonValue
  102. 102. sealed trait JsonValue case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue case class JsonArray(elems: List[JsonValue]) extends JsonValue case class JsonString(value: String) extends JsonValue case class JsonNumber(value: BigDecimal) extends JsonValue
  103. 103. sealed trait JsonValue case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue case class JsonArray(elems: List[JsonValue]) extends JsonValue case class JsonString(value: String) extends JsonValue case class JsonNumber(value: BigDecimal) extends JsonValue object JsonWriter { def write(json: JsonValue): String =
  104. 104. sealed trait JsonValue case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue case class JsonArray(elems: List[JsonValue]) extends JsonValue case class JsonString(value: String) extends JsonValue case class JsonNumber(value: BigDecimal) extends JsonValue object JsonWriter { def write(json: JsonValue): String = json match { case JsonObject(elems) => val entries = for { (key, value) <- elems } yield s""""$key: ${write(value)}"""" "{" + entries.mkString(", ") + "}" case JsonArray(elems) => "[" + elems.map(write).mkString(", ") + "]" case JsonString(value) => s""""$value"""" case JsonNumber(value) => value.toString } }
  105. 105. sealed trait JsonValue case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue case class JsonArray(elems: List[JsonValue]) extends JsonValue case class JsonString(value: String) extends JsonValue case class JsonNumber(value: BigDecimal) extends JsonValue object JsonWriter { def write(json: JsonValue): String = ... }
  106. 106. sealed trait JsonValue case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue case class JsonArray(elems: List[JsonValue]) extends JsonValue case class JsonString(value: String) extends JsonValue case class JsonNumber(value: BigDecimal) extends JsonValue object JsonWriter { def write(json: JsonValue): String = ... } trait JsonSerializable { def toJson: JsonValue } object JsonSerializer { def write(serializable: JsonSerializable) = JsonWriter.write(serializable.toJson) }
  107. 107. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
  108. 108. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
  109. 109. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
  110. 110. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable case class GeneralOrder(products: List[Product]) extends Order { def evaluate: BigDecimal = ... def toJson: JsonValue = JsonObject(Map( "type" -> JsonString("general"), "products" -> JsonArray(products.map(_.toJson)) )) }
  111. 111. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable case object CancelledOrder extends Order { def evaluate: BigDecimal = ... def toJson: JsonValue = JsonString("cancelled order") }
  112. 112. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable case class ComplexOrder(orders: List[Order]) extends Order { def evaluate: BigDecimal = ... def toJson: JsonValue = JsonObject(Map( "type" -> JsonString("complex"), "orders" -> JsonArray(orders.map(_.toJson))) ) }
  113. 113. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable case class BasicProduct(id: Int, price: BigDecimal) extends Product { def evaluate: BigDecimal = ... def toJson: JsonValue = JsonObject(Map( "type" -> JsonString("basic"), "id" -> JsonNumber(BigDecimal(id)), "price" -> JsonNumber(price) )) }
  114. 114. sealed trait Product extends Evaluatable[BigDecimal] with JsonSerializable case class DiscountedProduct(product: Product, discount: Double) extends Product { def evaluate: BigDecimal = ... def toJson: JsonValue = JsonObject(Map( "type" -> JsonString("discounted"), "product" -> product.toJson, "discount" -> JsonNumber(discount) )) }
  115. 115. sealed trait Product extends Evaluatable[BigDecimal] with JsonSerializable case object OutOfStock extends Product { def evaluate: BigDecimal = ... def toJson: JsonValue = JsonString("out of stock") }
  116. 116. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) } [info] OrderTest: [info] - should evaluate order [info] - should calculate average [info] - should serialize to json
  117. 117. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  118. 118. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  119. 119. Our goal: ● DRY principle ● Separation of concerns ● Epic decoupling ● Clean API ● Minimal Boilerplate
  120. 120. Act.3: “Re-inventing the Wheel”
  121. 121. trait Number[A] { def value: A def +(other: Number[A]): Number[A] def /(other: Number[A]): Number[A] def /(other: Int): Number[A] } case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] { def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value) def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value) def /(other: Int) = BigDecimalNumber(value / other) } case class IntNumber(value: Int) extends Number[Int] { def +(other: Number[Int]) = IntNumber(value + other.value) def /(other: Number[Int]) = IntNumber(value / other.value) def /(other: Int) = IntNumber(value / other) } case class DoubleNumber(value: Double) extends Number[Double] { def +(other: Number[Double]) = DoubleNumber(value + other.value) def /(other: Number[Double]) = DoubleNumber(value / other.value) def /(other: Int) = DoubleNumber(value / other) }
  122. 122. object Stat { def mean[A](xs: Seq[Number[A]): Number[A] = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value }
  123. 123. object Stat { def mean[A](xs: Seq[A]): A = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  124. 124. object Stat { def mean[A](xs: Seq[A]): A = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  125. 125. object Stat { def mean[A](xs: Seq[A]): A = xs.reduce(_ + _) / xs.size } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  126. 126. object Stat { def mean[A](xs: Seq[A]): A = } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  127. 127. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  128. 128. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  129. 129. trait Number[A] { def plus(n1: A, n2: A): A def divide(n1: A, n2: A): A def divide(n1: A, n2: Int): A }
  130. 130. trait Number[A] { def plus(n1: A, n2: A): A def divide(n1: A, n2: A): A def divide(n1: A, n2: Int): A } object Number { implicit object BigDecimalNumber extends Number[BigDecimal] { def plus(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 + n2 def divide(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 / n2 def divide(n1: BigDecimal, n2: Int): BigDecimal = n1 / n2 }
  131. 131. trait Number[A] { def plus(n1: A, n2: A): A def divide(n1: A, n2: A): A def divide(n1: A, n2: Int): A } object Number { implicit object BigDecimalNumber extends Number[BigDecimal] { def plus(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 + n2 def divide(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 / n2 def divide(n1: BigDecimal, n2: Int): BigDecimal = n1 / n2 } implicit object IntNumber extends Number[Int] { def plus(n1: Int, n2: Int): Int = n1 + n2 def divide(n1: Int, n2: Int): Int = n1 / n2 } }
  132. 132. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  133. 133. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Order { def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate))(BigDecimalNumber) }
  134. 134. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Order { import Number._ def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate))(BigDecimalNumber) }
  135. 135. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Order { import Number._ def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  136. 136. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Order { import Number._ def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  137. 137. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Number { implicit object BigDecimalNumber extends Number[BigDecimal] {..} implicit object IntNumber extends Number[Int] {..} }
  138. 138. object Stat { def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Number { implicit object BigDecimalNumber extends Number[BigDecimal] {..} implicit object IntNumber extends Number[Int] {..} implicit class NumberOps[A](a: A)(implicit number: Number[A]) { def +(other: A) = number.plus(a, other) def /(other: A) = number.divide(a, other) def /(other: Int) = number.divide(a, other) } }
  139. 139. object Stat { import Number._ def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Number { implicit object BigDecimalNumber extends Number[BigDecimal] {..} implicit object IntNumber extends Number[Int] {..} implicit class NumberOps[A](a: A)(implicit number: Number[A]) { def +(other: A) = number.plus(a, other) def /(other: A) = number.divide(a, other) def /(other: Int) = number.divide(a, other) } }
  140. 140. object Stat { import Number._ def mean[A](xs: Seq[A])(implicit number: Number[A]): A = number.divide(xs.reduce(number.plus),xs.size) } object Number { implicit object BigDecimalNumber extends Number[BigDecimal] {..} implicit object IntNumber extends Number[Int] {..} implicit class NumberOps[A](a: A)(implicit number: Number[A]) { def +(other: A) = number.plus(a, other) def /(other: A) = number.divide(a, other) def /(other: Int) = number.divide(a, other) } }
  141. 141. object Stat { import Number._ def mean[A](xs: Seq[A])(implicit number: Number[A]): A = xs.reduce(_ + _) / xs.size } object Number { implicit object BigDecimalNumber extends Number[BigDecimal] {..} implicit object IntNumber extends Number[Int] {..} implicit class NumberOps[A](a: A)(implicit number: Number[A]) { def +(other: A) = number.plus(a, other) def /(other: A) = number.divide(a, other) def /(other: Int) = number.divide(a, other) } }
  142. 142. object Stat { import Number._ def mean[A](xs: Seq[A])(implicit number: Number[A]): A = xs.reduce(_ + _) / xs.size } object Order { import Number._ def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  143. 143. [info] OrderTest: [info] - should evaluate order [info] - should calculate average [info] - should serialize to json
  144. 144. sealed trait JsonValue case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue case class JsonArray(elems: List[JsonValue]) extends JsonValue case class JsonString(value: String) extends JsonValue case class JsonNumber(value: BigDecimal) extends JsonValue object JsonWriter { def write(json: JsonValue): String = ... } trait JsonSerializable { def toJson: JsonValue } object JsonSerializer { def write(serializable: JsonSerializable) = JsonWriter.write(serializable.toJson) }
  145. 145. sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable case class GeneralOrder(products: List[Product]) extends Order { def evaluate: BigDecimal = ... def toJson: JsonValue = JsonObject(Map( "type" -> JsonString("general"), "products" -> JsonArray(products.map(_.toJson)) )) }
  146. 146. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) }
  147. 147. trait JsonSerializable { def toJson: JsonValue } object JsonSerializer { def write(serializable: JsonSerializable) = JsonWriter.write(serializable.toJson) }
  148. 148. object JsonSerializer { def write(serializable: JsonSerializable) = JsonWriter.write(serializable.toJson) }
  149. 149. object JsonSerializer { def write[A](a: A) = JsonWriter.write( toJson(a)) }
  150. 150. object JsonSerializer { def write[A](a: A)(implicit json: Json[A]) = JsonWriter.write(json.toJson(a)) }
  151. 151. object JsonSerializer { def write[A](a: A)(implicit json: Json[A]) = JsonWriter.write(json.toJson(a)) }
  152. 152. object JsonSerializer { def write[A](a: A)(implicit json: Json[A]) = JsonWriter.write(json.toJson(a)) }
  153. 153. trait Json[-A] { def toJson(a: A): JsonValue } object JsonSerializer { def write[A](a: A)(implicit json: Json[A]) = JsonWriter.write(json.toJson(a)) }
  154. 154. object OrderJson { implicit object ProductToJson extends Json[Product] { def toJson(product: Product): JsonValue = ... } implicit object OrderToJson extends Json[Order] { def toJson(order: Order): JsonValue = ... } }
  155. 155. object OrderJson { implicit object ProductToJson extends Json[Product] { def toJson(product: Product): JsonValue = ... } implicit object OrderToJson extends Json[Order] { def toJson(order: Order): JsonValue = ... } }
  156. 156. object OrderJson { implicit object ProductToJson extends Json[Product] { def toJson(product: Product): JsonValue = ... } implicit object OrderToJson extends Json[Order] { def toJson(order: Order): JsonValue = ... } }
  157. 157. implicit object ProductToJson extends Json[Product] { def toJson(product: Product): JsonValue = product match { case BasicProduct(id, price) => JsonObject(Map( "type" -> JsonString("basic"), "id" -> JsonNumber(BigDecimal(id)), "price" -> JsonNumber(price) )) case DiscountedProduct(product, discount) => JsonObject(Map( "type" -> JsonString("discounted"), "product" -> toJson(product), "discount" -> JsonNumber(discount) )) case OutOfStock() => JsonString("out of stock") } }
  158. 158. implicit object OrderToJson extends Json[Order] { def toJson(order: Order): JsonValue = order match { case GeneralOrder(products) => JsonObject(Map( "type" -> JsonString("general"), "products" -> JsonArray(products.map(ProductToJson.toJson)) )) case ComplexOrder(orders) => JsonObject(Map( "type" -> JsonString("complex"), "orders" -> JsonArray(orders.map(toJson)) )) case CancelledOrder() => JsonString("cancelled order") } }
  159. 159. object OrderJson { implicit object ProductToJson extends Json[Product] { def toJson(product: Product): JsonValue = ... } implicit object OrderToJson extends Json[Order] { def toJson(order: Order): JsonValue = ... } }
  160. 160. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" JsonSerializer.write(order) should equal(expectedJson) }
  161. 161. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" import OrderJson._ JsonSerializer.write(order) should equal(expectedJson) }
  162. 162. test("should serialize to json") { val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil) val o2 = CancelledOrder val order = ComplexOrder(o1 :: o2 :: Nil) val expectedJson = """{ "type: "complex"", "orders: [{ "type: "general"", "products: [{"type: "basic"", "id: 10", "price: 10.2"}]" }, "cancelled order"]" }""" import OrderJson._ JsonSerializer.write(order) should equal(expectedJson) } [info] OrderTest: [info] - should evaluate order [info] - should calculate average [info] - should serialize to json
  163. 163. trait Evaluatable[T] { def evaluate: T } sealed trait Product extends Evaluatable[BigDecimal] case class BasicProduct(id: Int, price: BigDecimal) extends Product { def evaluate: BigDecimal = price } case class DiscountedProduct(product: Product, discount: Double) extends Product { def evaluate: BigDecimal = product.evaluate * (1 - discount) } case object OutOfStock extends Product { def evaluate: BigDecimal = BigDecimal("0.0") }
  164. 164. trait Evaluatable[T] { def evaluate: T } sealed trait Order extends Evaluatable[BigDecimal] case object CancelledOrder extends Order { def evaluate: BigDecimal = BigDecimal("0.0") } case class ComplexOrder(orders: List[Order]) extends Order { def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + o.evaluate } } case class GeneralOrder(products: List[Product]) extends Order { def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + p.evaluate } }
  165. 165. trait Evaluate[-A, T] { def evaluate(a: A): T } object Order { implicit object ProductEvaluate extends Evaluate[Product, BigDecimal] { def evaluate(product: Product): BigDecimal = product match { case BasicProduct(id, price) => price case DiscountedProduct(discounted, discount) => evaluate(discounted) * (1 - discount) case OutOfStock() => BigDecimal("0.0") }} implicit object OrderEvaluate extends Evaluate[Order, BigDecimal] { def evaluate(order: Order): BigDecimal = order match { case GeneralOrder(products) => products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + ProductEvaluate.evaluate(p) } case ComplexOrder(orders) => orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + evaluate(o) } case CancelledOrder() => BigDecimal("0.0") }}}
  166. 166. trait Evaluate[-A, T] { def evaluate(a: A): T } object Order { implicit object ProductEvaluate extends Evaluate[Product, BigDecimal] { def evaluate(product: Product): BigDecimal = product match { case BasicProduct(id, price) => price case DiscountedProduct(discounted, discount) => evaluate(discounted) * (1 - discount) case OutOfStock() => BigDecimal("0.0") }} implicit object OrderEvaluate extends Evaluate[Order, BigDecimal] { def evaluate(order: Order): BigDecimal = order match { case GeneralOrder(products) => products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + ProductEvaluate.evaluate(p) } case ComplexOrder(orders) => orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + evaluate(o) } case CancelledOrder() => BigDecimal("0.0") }}}
  167. 167. trait Evaluate[-A, T] { def evaluate(a: A): T } object Order { implicit object ProductEvaluate extends Evaluate[Product, BigDecimal] { def evaluate(product: Product): BigDecimal = product match { case BasicProduct(id, price) => price case DiscountedProduct(discounted, discount) => evaluate(discounted) * (1 - discount) case OutOfStock() => BigDecimal("0.0") }} implicit object OrderEvaluate extends Evaluate[Order, BigDecimal] { def evaluate(order: Order): BigDecimal = order match { case GeneralOrder(products) => products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + ProductEvaluate.evaluate(p) } case ComplexOrder(orders) => orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + evaluate(o) } case CancelledOrder() => BigDecimal("0.0") }}}
  168. 168. trait Evaluate[-A, T] { def evaluate(a: A): T } object Order { implicit object ProductEvaluate extends Evaluate[Product, BigDecimal] { def evaluate(product: Product): BigDecimal = product match { case BasicProduct(id, price) => price case DiscountedProduct(discounted, discount) => evaluate(discounted) * (1 - discount) case OutOfStock() => BigDecimal("0.0") }} implicit object OrderEvaluate extends Evaluate[Order, BigDecimal] { def evaluate(order: Order): BigDecimal = order match { case GeneralOrder(products) => products.foldLeft(BigDecimal("0.0")) { case (acc, p) => acc + ProductEvaluate.evaluate(p) } case ComplexOrder(orders) => orders.foldLeft(BigDecimal("0.0")) { case (acc, o) => acc + evaluate(o) } case CancelledOrder() => BigDecimal("0.0") }}}
  169. 169. trait Evaluate[-A, T] { def evaluate(a: A): T } object Order { import Number._ def average(orders: Seq[Order]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  170. 170. trait Evaluate[-A, T] { def evaluate(a: A): T } object Evaluate { implicit class EvaluateOps[-A, T](a: A)(implicit ev: Evaluate[A, T]) { def evaluate = ev.evaluate(a) } } object Order { import Number._ def average(orders: Seq[Order])(implicit ev: Evaluate[Order, BigDecimal]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  171. 171. trait Evaluate[-A, T] { def evaluate(a: A): T } object Evaluate { implicit class EvaluateOps[-A, T](a: A)(implicit ev: Evaluate[A, T]) { def evaluate = ev.evaluate(a) } } object Order { import Number._ def average(orders: Seq[Order])(implicit ev: Evaluate[Order, BigDecimal]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  172. 172. trait Evaluate[-A, T] { def evaluate(a: A): T } object Evaluate { implicit class EvaluateOps[-A, T](a: A)(implicit ev: Evaluate[A, T]) { def evaluate = ev.evaluate(a) } } object Order { import Number._ def average(orders: Seq[Order])(implicit ev: Evaluate[Order, BigDecimal]): BigDecimal = Stat.mean(orders.map(_.evaluate)) }
  173. 173. sealed trait Order case class GeneralOrder(products: List[Product]) extends Order case object CancelledOrder extends Order case class ComplexOrder(orders: List[Order]) extends Order sealed trait Product case class BasicProduct(id: Int, price: BigDecimal) extends Product case class DiscountedProduct(product: Product, discount: Double) extends Product case object OutOfStock extends Product
  174. 174. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  175. 175. Domain & Feature Requests Feature Requests: ● Present current state of orders (JSON) ● Calculate final checkout ● Calculate average Order price ● “Simplify” Orders
  176. 176. Act.4: “Enlightenment”
  177. 177. Research
  178. 178. Definitions
  179. 179. Definitions ● Type classes
  180. 180. Definitions ● Type classes ○ Ad-hoc polymorphism ● Abstract Data Type (ADT)
  181. 181. Our goal: ● DRY principle ● Separation of concerns ● Epic decoupling ● Clean API ● Minimal Boilerplate
  182. 182. Where to go next 1. Semigroup 2. Monoid 3. Functor 4. Applicative 5. Monad! All are just type classes with some laws. That’s it!
  183. 183. Links & Resources ● https://github.com/rabbitonweb/scala_typeclasses ● https://inoio.de/blog/2014/07/19/type-class-101-semigroup/ ● http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala- part-12-type-classes.html ● https://www.youtube.com/watch?v=sVMES4RZF-8 ● http://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf ● https://www.destroyallsoftware.com/misc/reject.pdf ● http://southpark.cc.com/avatar ● http://www.tandemic.com/wp-content/uploads/Definition.png
  184. 184. And that’s all folks!
  185. 185. And that’s all folks! Paweł Szulc
  186. 186. And that’s all folks! Paweł Szulc twitter: @rabbitonweb
  187. 187. And that’s all folks! Paweł Szulc twitter: @rabbitonweb blog: http://rabbitonweb.com
  188. 188. And that’s all folks! Paweł Szulc twitter: @rabbitonweb blog: http://rabbitonweb.com github: https://github.com/rabbitonweb
  189. 189. And that’s all folks! Paweł Szulc twitter: @rabbitonweb blog: http://rabbitonweb.com github: https://github.com/rabbitonweb Questions?
  190. 190. And that’s all folks! Paweł Szulc twitter: @rabbitonweb blog: http://rabbitonweb.com github: https://github.com/rabbitonweb Questions? Thank you!
  191. 191. Bonus!
  192. 192. object Stat { import Number._ def mean[A](xs: Seq[A])(implicit number: Number[A]): A = xs.reduce(_ + _) / xs.size }
  193. 193. object Stat { import Number._ def mean[A](xs: Seq[A])(implicit number: Number[A]): A = xs.reduce(_ + _) / xs.size } object Stat { import Number._ def mean[A : Number](xs: Seq[A]): A = xs.reduce(_ + _) / xs.size }
  194. 194. object Stat { import Number._ def mean[A](xs: Seq[A])(implicit number: Number[A]): A = xs.reduce(_ + _) / xs.size } object Stat { import Number._ def mean[A : Number](xs: Seq[A]): A = xs.reduce(_ + _) / xs.size }
  195. 195. object JsonSerializer { def write[A](a: A)(implicit json: Json[A]) = JsonWriter.write(json.toJson(a)) }
  196. 196. object JsonSerializer { def write[A](a: A)(implicit json: Json[A]) = JsonWriter.write(json.toJson(a)) } object JsonSerializer { def write[A : Json](a: A) = JsonWriter.write(implicitly[Json[A]].toJson(a)) }
  197. 197. object JsonSerializer { def write[A](a: A)(implicit json: Json[A]) = JsonWriter.write(json.toJson(a)) } object JsonSerializer { def write[A : Json](a: A) = JsonWriter.write(implicitly[Json[A]].toJson(a)) }

×