Introduction to type classes in 30 min

Post on 14-Apr-2017

1.257 views 0 download

Transcript of Introduction to type classes in 30 min

Having a cake and eating it too.

Introduction to Type classes (in Scala)

“The Story of a Blackout”

In 4 acts

Act.1: “Loss & Grief”

503 Service Temporarily Unavailablenginx

503 Service Temporarily Unavailablenginx

The 5 Stages of Loss and Grief

The 5 Stages of Loss and Grief

1. Denial

The 5 Stages of Loss and Grief

1. Denial2. Anger

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining4. Depression

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining4. Depression5. Acceptance

Act.2: “The Mess”

auction

auctionify

auctionify.io

Domain & Feature RequestsDomain:

● Users place Orders in the auction system

Order

Domain & Feature RequestsDomain:

● 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

Domain & Feature RequestsDomain:

● 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

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

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

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

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

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

}

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

}

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

}

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

}

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

}

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

}

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

}

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

Subtype Polymorphism

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

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

}

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

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

}

}

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

}

}

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

}

}

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

}

}

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

}

}

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

}

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

}

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

}

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

}

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

}

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

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

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

}

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

}

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

}

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

}

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

}

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

}

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

}

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

object Stat {

def mean(xs: Seq[BigDecimal]): BigDecimal = ???

}

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

}

object Stat {

def mean(xs: Seq[BigDecimal]): BigDecimal =

xs.reduce(_ + _) / xs.size

}

AWESOME!I just need it to work for Doubles and Ints as well! Can you make it more generic? Thx!

object Stat {

def mean(xs: Seq[BigDecimal]): BigDecimal =

xs.reduce(_ + _) / xs.size

}

object Stat {

def mean(xs: Seq[Number]): Number =

xs.reduce(_ + _) / xs.size

}

object Stat {

def mean(xs: Seq[Number]): Number =

xs.reduce(_ + _) / xs.size

}

trait Number[A] {

def value: A

def +(other: Number[A]): Number[A]

def /(other: Number[A]): Number[A]

def /(other: Int): Number[A]

}

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)

}

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)

}

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)

}

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)

}

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

}

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

}

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

}

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

}

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

}

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

}

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

}

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

}

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

}

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

}

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

}

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

}

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

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

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)

}

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)

}

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)

}

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)

}

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)

}

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)

}

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] ^

object JsonSerializer {

def write(order: Order): String = ...

}

I have few objects I also need to serialize to JSON. Can you make

your code a bit more generic? Thanks!!!

object JsonSerializer {

def write(order: Order): String = ...

}

object JsonSerializer {

def write(sth: ???): String = ...

}

object JsonSerializer {

def write(serializable: JsonSerializable) = ...

}

object JsonSerializer {

def write(serializable: JsonSerializable) = ...

}

trait JsonSerializable {

def toJson: JsonValue

}

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

trait JsonSerializable {

def toJson: JsonValue

}

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

trait JsonSerializable {

def toJson: JsonValue

}

sealed trait JsonValue

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

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 =

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

}

}

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

}

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)

}

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

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

))

}

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

case object CancelledOrder extends Order {

def evaluate: BigDecimal = ...

def toJson: JsonValue = JsonString("cancelled order")

}

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

)

}

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)

))

}

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)

))

}

sealed trait Product extends Evaluatable[BigDecimal] with JsonSerializable

case object OutOfStock extends Product {

def evaluate: BigDecimal = ...

def toJson: JsonValue = JsonString("out of stock")

}

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

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Our goal:

● DRY principle● Separation of concerns● Epic decoupling● Clean API● Minimal Boilerplate

Act.3: “Re-inventing the Wheel”

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)

}

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

}

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

}

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

}

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

}

object Stat {

def mean[A](xs: Seq[A]): A =

}

object Order {

def average(orders: Seq[Order]): BigDecimal =

Stat.mean(orders.map(_.evaluate))

}

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

}

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

}

trait Number[A] {

def plus(n1: A, n2: A): A

def divide(n1: A, n2: A): A

def divide(n1: A, n2: Int): A

}

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

}

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

}

}

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

}

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)

}

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)

}

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

}

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

}

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

}

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)

}

}

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)

}

}

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)

}

}

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)

}

}

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

}

[info] OrderTest:

[info] - should evaluate order

[info] - should calculate average

[info] - should serialize to json

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)

}

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

))

}

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)

}

trait JsonSerializable {

def toJson: JsonValue

}

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

object JsonSerializer {

def write[A](a: A) =

JsonWriter.write( toJson(a))

}

object JsonSerializer {

def write[A](a: A)(implicit json: Json[A]) =

JsonWriter.write(json.toJson(a))

}

object JsonSerializer {

def write[A](a: A)(implicit json: Json[A]) =

JsonWriter.write(json.toJson(a))

}

object JsonSerializer {

def write[A](a: A)(implicit json: Json[A]) =

JsonWriter.write(json.toJson(a))

}

trait Json[-A] {

def toJson(a: A): JsonValue

}

object JsonSerializer {

def write[A](a: A)(implicit json: Json[A]) =

JsonWriter.write(json.toJson(a))

}

object OrderJson {

implicit object ProductToJson extends Json[Product] {

def toJson(product: Product): JsonValue = ...

}

implicit object OrderToJson extends Json[Order] {

def toJson(order: Order): JsonValue = ...

}

}

object OrderJson {

implicit object ProductToJson extends Json[Product] {

def toJson(product: Product): JsonValue = ...

}

implicit object OrderToJson extends Json[Order] {

def toJson(order: Order): JsonValue = ...

}

}

object OrderJson {

implicit object ProductToJson extends Json[Product] {

def toJson(product: Product): JsonValue = ...

}

implicit object OrderToJson extends Json[Order] {

def toJson(order: Order): JsonValue = ...

}

}

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

}

}

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

}

}

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

}

}

object OrderJson {

implicit object ProductToJson extends Json[Product] {

def toJson(product: Product): JsonValue = ...

}

implicit object OrderToJson extends Json[Order] {

def toJson(order: Order): JsonValue = ...

}

}

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)

}

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)

}

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

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

}

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

}

}

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

}}}

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

}}}

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

}}}

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

}}}

trait Evaluate[-A, T] { def evaluate(a: A): T }

object Order {

import Number._

def average(orders: Seq[Order]): BigDecimal =

Stat.mean(orders.map(_.evaluate))

}

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

}

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

}

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

}

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

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Domain & Feature RequestsFeature Requests:

● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders

Act.4: “Enlightenment”

Research

Definitions

Definitions● Type classes

Definitions● Type classes

○ Ad-hoc polymorphism

● Abstract Data Type (ADT)

Our goal:

● DRY principle● Separation of concerns● Epic decoupling● Clean API● Minimal Boilerplate

Where to go next1. Semigroup2. Monoid3. Functor4. Applicative5. Monad!

All are just type classes with some laws. That’s it!

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

And that’s all folks!

And that’s all folks!

Paweł Szulc

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

github: https://github.com/rabbitonweb

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

github: https://github.com/rabbitonweb

Questions?

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

github: https://github.com/rabbitonweb

Questions?

Thank you!

Bonus!

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

}

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

}

object JsonSerializer {

def write[A](a: A)(implicit json: Json[A]) =

JsonWriter.write(json.toJson(a))

}

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

}

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

}