introduction to type classes in 30 min

227
Having a cake and eating it too. Introduction to Type classes (in Scala)

Upload: pawel-szulc

Post on 14-Apr-2017

1.256 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Introduction to type classes in 30 min

Having a cake and eating it too.

Introduction to Type classes (in Scala)

Page 2: Introduction to type classes in 30 min
Page 3: Introduction to type classes in 30 min

“The Story of a Blackout”

In 4 acts

Page 4: Introduction to type classes in 30 min

Act.1: “Loss & Grief”

Page 5: Introduction to type classes in 30 min
Page 6: Introduction to type classes in 30 min
Page 7: Introduction to type classes in 30 min
Page 8: Introduction to type classes in 30 min
Page 9: Introduction to type classes in 30 min

503 Service Temporarily Unavailablenginx

Page 10: Introduction to type classes in 30 min

503 Service Temporarily Unavailablenginx

Page 11: Introduction to type classes in 30 min
Page 12: Introduction to type classes in 30 min
Page 13: Introduction to type classes in 30 min

The 5 Stages of Loss and Grief

Page 14: Introduction to type classes in 30 min

The 5 Stages of Loss and Grief

1. Denial

Page 15: Introduction to type classes in 30 min

The 5 Stages of Loss and Grief

1. Denial2. Anger

Page 16: Introduction to type classes in 30 min

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining

Page 17: Introduction to type classes in 30 min

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining4. Depression

Page 18: Introduction to type classes in 30 min

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining4. Depression5. Acceptance

Page 19: Introduction to type classes in 30 min
Page 20: Introduction to type classes in 30 min

Act.2: “The Mess”

Page 21: Introduction to type classes in 30 min

auction

Page 22: Introduction to type classes in 30 min

auctionify

Page 23: Introduction to type classes in 30 min

auctionify.io

Page 24: Introduction to type classes in 30 min

Domain & Feature RequestsDomain:

● Users place Orders in the auction system

Order

Page 25: Introduction to type classes in 30 min

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

Page 26: Introduction to type classes in 30 min

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

Page 27: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 28: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 29: Introduction to type classes in 30 min

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

Page 30: Introduction to type classes in 30 min

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

Page 31: Introduction to type classes in 30 min

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

Page 32: Introduction to type classes in 30 min

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

}

Page 33: Introduction to type classes in 30 min

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

}

Page 34: Introduction to type classes in 30 min

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

}

Page 35: Introduction to type classes in 30 min

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

}

Page 36: Introduction to type classes in 30 min

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

}

Page 37: Introduction to type classes in 30 min

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

}

Page 38: Introduction to type classes in 30 min

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

}

Page 39: Introduction to type classes in 30 min

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

Page 40: Introduction to type classes in 30 min
Page 41: Introduction to type classes in 30 min
Page 42: Introduction to type classes in 30 min
Page 43: Introduction to type classes in 30 min
Page 44: Introduction to type classes in 30 min
Page 45: Introduction to type classes in 30 min
Page 46: Introduction to type classes in 30 min

Subtype Polymorphism

Page 47: Introduction to type classes in 30 min

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

Page 48: Introduction to type classes in 30 min

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

}

Page 49: Introduction to type classes in 30 min

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

Page 50: Introduction to type classes in 30 min

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

}

}

Page 51: Introduction to type classes in 30 min

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

}

}

Page 52: Introduction to type classes in 30 min

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

}

}

Page 53: Introduction to type classes in 30 min

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

}

}

Page 54: Introduction to type classes in 30 min

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

}

}

Page 55: Introduction to type classes in 30 min

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

}

Page 56: Introduction to type classes in 30 min

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

}

Page 57: Introduction to type classes in 30 min

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

}

Page 58: Introduction to type classes in 30 min

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

}

Page 59: Introduction to type classes in 30 min

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

}

Page 60: Introduction to type classes in 30 min

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

Page 61: Introduction to type classes in 30 min
Page 62: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 63: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 64: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 65: Introduction to type classes in 30 min

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

}

Page 66: Introduction to type classes in 30 min

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

}

Page 67: Introduction to type classes in 30 min

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

}

Page 68: Introduction to type classes in 30 min

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

}

Page 69: Introduction to type classes in 30 min

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

}

Page 70: Introduction to type classes in 30 min

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

}

Page 71: Introduction to type classes in 30 min

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

}

Page 72: Introduction to type classes in 30 min

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

Page 73: Introduction to type classes in 30 min

object Stat {

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

}

Page 74: Introduction to type classes in 30 min

object Stat {

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

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

}

Page 75: Introduction to type classes in 30 min

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

}

Page 76: Introduction to type classes in 30 min
Page 77: Introduction to type classes in 30 min

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

Page 78: Introduction to type classes in 30 min

object Stat {

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

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

}

Page 79: Introduction to type classes in 30 min

object Stat {

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

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

}

Page 80: Introduction to type classes in 30 min

object Stat {

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

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

}

Page 81: Introduction to type classes in 30 min
Page 82: Introduction to type classes in 30 min
Page 83: Introduction to type classes in 30 min

trait Number[A] {

def value: A

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

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

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

}

Page 84: Introduction to type classes in 30 min

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)

}

Page 85: Introduction to type classes in 30 min

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)

}

Page 86: Introduction to type classes in 30 min

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)

}

Page 87: Introduction to type classes in 30 min

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)

}

Page 88: Introduction to type classes in 30 min

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

}

Page 89: Introduction to type classes in 30 min

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

}

Page 90: Introduction to type classes in 30 min

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

}

Page 91: Introduction to type classes in 30 min

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

}

Page 92: Introduction to type classes in 30 min

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

}

Page 93: Introduction to type classes in 30 min

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

}

Page 94: Introduction to type classes in 30 min

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

}

Page 95: Introduction to type classes in 30 min

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

}

Page 96: Introduction to type classes in 30 min

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

}

Page 97: Introduction to type classes in 30 min

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

}

Page 98: Introduction to type classes in 30 min

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

}

Page 99: Introduction to type classes in 30 min

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

}

Page 100: Introduction to type classes in 30 min

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

Page 101: Introduction to type classes in 30 min
Page 102: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 103: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 104: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 105: Introduction to type classes in 30 min

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)

}

Page 106: Introduction to type classes in 30 min

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)

}

Page 107: Introduction to type classes in 30 min

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)

}

Page 108: Introduction to type classes in 30 min

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)

}

Page 109: Introduction to type classes in 30 min

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)

}

Page 110: Introduction to type classes in 30 min

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)

}

Page 111: Introduction to type classes in 30 min

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

Page 112: Introduction to type classes in 30 min

object JsonSerializer {

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

}

Page 113: Introduction to type classes in 30 min
Page 114: Introduction to type classes in 30 min
Page 115: Introduction to type classes in 30 min

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

your code a bit more generic? Thanks!!!

Page 116: Introduction to type classes in 30 min

object JsonSerializer {

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

}

Page 117: Introduction to type classes in 30 min

object JsonSerializer {

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

}

Page 118: Introduction to type classes in 30 min

object JsonSerializer {

def write(serializable: JsonSerializable) = ...

}

Page 119: Introduction to type classes in 30 min

object JsonSerializer {

def write(serializable: JsonSerializable) = ...

}

trait JsonSerializable {

def toJson: JsonValue

}

Page 120: Introduction to type classes in 30 min

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

trait JsonSerializable {

def toJson: JsonValue

}

Page 121: Introduction to type classes in 30 min

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

trait JsonSerializable {

def toJson: JsonValue

}

Page 122: Introduction to type classes in 30 min

sealed trait JsonValue

Page 123: Introduction to type classes in 30 min

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

Page 124: Introduction to type classes in 30 min

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 =

Page 125: Introduction to type classes in 30 min

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

}

}

Page 126: Introduction to type classes in 30 min

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

}

Page 127: Introduction to type classes in 30 min

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)

}

Page 128: Introduction to type classes in 30 min

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

Page 129: Introduction to type classes in 30 min

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

Page 130: Introduction to type classes in 30 min

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

Page 131: Introduction to type classes in 30 min

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

))

}

Page 132: Introduction to type classes in 30 min

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

case object CancelledOrder extends Order {

def evaluate: BigDecimal = ...

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

}

Page 133: Introduction to type classes in 30 min

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

)

}

Page 134: Introduction to type classes in 30 min

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)

))

}

Page 135: Introduction to type classes in 30 min

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)

))

}

Page 136: Introduction to type classes in 30 min

sealed trait Product extends Evaluatable[BigDecimal] with JsonSerializable

case object OutOfStock extends Product {

def evaluate: BigDecimal = ...

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

}

Page 137: Introduction to type classes in 30 min

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

Page 138: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 139: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 140: Introduction to type classes in 30 min
Page 141: Introduction to type classes in 30 min
Page 142: Introduction to type classes in 30 min
Page 143: Introduction to type classes in 30 min
Page 144: Introduction to type classes in 30 min

Our goal:

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

Page 145: Introduction to type classes in 30 min

Act.3: “Re-inventing the Wheel”

Page 146: Introduction to type classes in 30 min

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)

}

Page 147: Introduction to type classes in 30 min

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

}

Page 148: Introduction to type classes in 30 min

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

}

Page 149: Introduction to type classes in 30 min

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

}

Page 150: Introduction to type classes in 30 min

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

}

Page 151: Introduction to type classes in 30 min

object Stat {

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

}

object Order {

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

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

}

Page 152: Introduction to type classes in 30 min

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

}

Page 153: Introduction to type classes in 30 min

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

}

Page 154: Introduction to type classes in 30 min

trait Number[A] {

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

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

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

}

Page 155: Introduction to type classes in 30 min

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

}

Page 156: Introduction to type classes in 30 min

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

}

}

Page 157: Introduction to type classes in 30 min

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

}

Page 158: Introduction to type classes in 30 min

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)

}

Page 159: Introduction to type classes in 30 min

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)

}

Page 160: Introduction to type classes in 30 min

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

}

Page 161: Introduction to type classes in 30 min

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

}

Page 162: Introduction to type classes in 30 min

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

}

Page 163: Introduction to type classes in 30 min

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)

}

}

Page 164: Introduction to type classes in 30 min

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)

}

}

Page 165: Introduction to type classes in 30 min

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)

}

}

Page 166: Introduction to type classes in 30 min

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)

}

}

Page 167: Introduction to type classes in 30 min

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

}

Page 168: Introduction to type classes in 30 min

[info] OrderTest:

[info] - should evaluate order

[info] - should calculate average

[info] - should serialize to json

Page 169: Introduction to type classes in 30 min
Page 170: Introduction to type classes in 30 min
Page 171: Introduction to type classes in 30 min

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)

}

Page 172: Introduction to type classes in 30 min

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

))

}

Page 173: Introduction to type classes in 30 min

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)

}

Page 174: Introduction to type classes in 30 min

trait JsonSerializable {

def toJson: JsonValue

}

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

Page 175: Introduction to type classes in 30 min

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

Page 176: Introduction to type classes in 30 min

object JsonSerializer {

def write[A](a: A) =

JsonWriter.write( toJson(a))

}

Page 177: Introduction to type classes in 30 min

object JsonSerializer {

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

JsonWriter.write(json.toJson(a))

}

Page 178: Introduction to type classes in 30 min

object JsonSerializer {

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

JsonWriter.write(json.toJson(a))

}

Page 179: Introduction to type classes in 30 min

object JsonSerializer {

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

JsonWriter.write(json.toJson(a))

}

Page 180: Introduction to type classes in 30 min

trait Json[-A] {

def toJson(a: A): JsonValue

}

object JsonSerializer {

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

JsonWriter.write(json.toJson(a))

}

Page 181: Introduction to type classes in 30 min

object OrderJson {

implicit object ProductToJson extends Json[Product] {

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

}

implicit object OrderToJson extends Json[Order] {

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

}

}

Page 182: Introduction to type classes in 30 min

object OrderJson {

implicit object ProductToJson extends Json[Product] {

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

}

implicit object OrderToJson extends Json[Order] {

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

}

}

Page 183: Introduction to type classes in 30 min

object OrderJson {

implicit object ProductToJson extends Json[Product] {

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

}

implicit object OrderToJson extends Json[Order] {

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

}

}

Page 184: Introduction to type classes in 30 min

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

}

}

Page 185: Introduction to type classes in 30 min

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

}

}

Page 186: Introduction to type classes in 30 min

object OrderJson {

implicit object ProductToJson extends Json[Product] {

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

}

implicit object OrderToJson extends Json[Order] {

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

}

}

Page 187: Introduction to type classes in 30 min

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)

}

Page 188: Introduction to type classes in 30 min

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)

}

Page 189: Introduction to type classes in 30 min

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

Page 190: Introduction to type classes in 30 min
Page 191: Introduction to type classes in 30 min

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

}

Page 192: Introduction to type classes in 30 min

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

}

}

Page 193: Introduction to type classes in 30 min

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

}}}

Page 194: Introduction to type classes in 30 min

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

}}}

Page 195: Introduction to type classes in 30 min

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

}}}

Page 196: Introduction to type classes in 30 min

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

}}}

Page 197: Introduction to type classes in 30 min

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

object Order {

import Number._

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

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

}

Page 198: Introduction to type classes in 30 min

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

}

Page 199: Introduction to type classes in 30 min

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

}

Page 200: Introduction to type classes in 30 min

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

}

Page 201: Introduction to type classes in 30 min
Page 202: Introduction to type classes in 30 min

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

Page 203: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 204: Introduction to type classes in 30 min

Domain & Feature RequestsFeature Requests:

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

Page 205: Introduction to type classes in 30 min
Page 206: Introduction to type classes in 30 min

Act.4: “Enlightenment”

Page 207: Introduction to type classes in 30 min

Research

Page 208: Introduction to type classes in 30 min

Definitions

Page 209: Introduction to type classes in 30 min

Definitions● Type classes

Page 210: Introduction to type classes in 30 min

Definitions● Type classes

○ Ad-hoc polymorphism

● Abstract Data Type (ADT)

Page 211: Introduction to type classes in 30 min

Our goal:

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

Page 212: Introduction to type classes in 30 min

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

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

Page 213: Introduction to type classes in 30 min

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

Page 214: Introduction to type classes in 30 min

And that’s all folks!

Page 215: Introduction to type classes in 30 min

And that’s all folks!

Paweł Szulc

Page 216: Introduction to type classes in 30 min

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

Page 217: Introduction to type classes in 30 min

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

Page 218: Introduction to type classes in 30 min

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

github: https://github.com/rabbitonweb

Page 219: Introduction to type classes in 30 min

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

github: https://github.com/rabbitonweb

Questions?

Page 220: Introduction to type classes in 30 min

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

github: https://github.com/rabbitonweb

Questions?

Thank you!

Page 221: Introduction to type classes in 30 min

Bonus!

Page 222: Introduction to type classes in 30 min

object Stat {

import Number._

def mean[A](xs: Seq[A])(implicit number: Number[A]): A =

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

}

Page 223: Introduction to type classes in 30 min

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

}

Page 224: Introduction to type classes in 30 min

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

}

Page 225: Introduction to type classes in 30 min

object JsonSerializer {

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

JsonWriter.write(json.toJson(a))

}

Page 226: Introduction to type classes in 30 min

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

}

Page 227: Introduction to type classes in 30 min

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

}