249
Real-World Gobbledygook What is all the fuss with all those monoids and semigroups I keep hearing about?

Real world gobbledygook

Embed Size (px)

Citation preview

Page 1: Real world gobbledygook

Real-World Gobbledygook

What is all the fuss with all those monoids and semigroups I keep hearing about?

Page 2: Real world gobbledygook

Scala?

Page 3: Real world gobbledygook

public class MyClass {

private String someField;

public MyClass(String someField) {

this.someField = someField;

}

public String someField() {

return someField;

}

public String concat(String me) {

return someField + me;

}

public String hasCode() { .. }

public Boolean equals(Object obj) { .. }

}

Page 4: Real world gobbledygook

public class MyClass {

private String someField;

public MyClass(String someField) {

this.someField = someField;

}

public String someField() {

return someField;

}

public String concat(String me) {

return someField + me;

}

public String hasCode() { .. }

public Boolean equals(Object obj) { .. }

}

Page 5: Real world gobbledygook

class MyClass {

private String someField;

public MyClass(String someField) {

this.someField = someField;

}

String someField() {

return someField;

}

String concat(String me) {

return someField + me;

}

String hasCode() { .. }

Boolean equals(Object obj) { .. }

}

Page 6: Real world gobbledygook

class MyClass {

private String someField;

public MyClass(String someField) {

this.someField = someField;

}

String someField() {

return someField;

}

String concat(String me) {

return someField + me;

}

String hasCode() { .. }

Boolean equals(Object obj) { .. }

}

Page 7: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

String someField() {

return someField;

}

String concat(me: SomeField) {

return someField + me;

}

String hasCode() { .. }

Boolean equals(me: Object) { .. }

}

Page 8: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

String someField() {

return someField;

}

String concat(me: SomeField) {

return someField + me;

}

String hasCode() { .. }

Boolean equals(me: Object) { .. }

}

Page 9: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

String someField() {

return someField;

}

String concat(me: SomeField) {

return someField + me;

}

String hasCode() { .. }

Boolean equals(me: Object) { .. }

}

Page 10: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

def someField(): String = {

return someField;

}

def concat(me: SomeField): String = {

return someField + me;

}

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 11: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

def someField(): String = {

return someField;

}

def concat(me: SomeField): String = {

return someField + me;

}

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 12: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

def someField(): String = {

return someField;

}

def concat(me: SomeField): String = {

return someField + me;

}

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 13: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

def someField(): String = {

someField;

}

def concat(me: SomeField): String = {

someField + me;

}

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 14: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

def someField(): String = {

someField;

}

def concat(me: SomeField): String = {

someField + me;

}

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 15: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

def someField(): String = someField;

def concat(me: SomeField): String = someField + me;

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 16: Real world gobbledygook

class MyClass {

private someField: String;

public MyClass(someField: String) {

this.someField = someField;

}

def someField(): String = someField;

def concat(me: SomeField): String = someField + me;

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 17: Real world gobbledygook

class MyClass {

private someField: String

public MyClass(someField: String) {

this.someField = someField

}

def someField(): String = someField

def concat(me: SomeField): String = someField + me

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 18: Real world gobbledygook

class MyClass {

private someField: String

public MyClass(someField: String) {

this.someField = someField

}

def someField(): String = someField

def concat(me: SomeField): String = someField + me

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 19: Real world gobbledygook

class MyClass(someField: String) {

def someField(): String = someField

def concat(me: SomeField): String = someField + me

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 20: Real world gobbledygook

class MyClass(someField: String) {

def someField(): String = someField

def concat(me: SomeField): String = someField + me

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 21: Real world gobbledygook

class MyClass(val someField: String) {

def concat(me: SomeField): String = someField + me

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 22: Real world gobbledygook

class MyClass(val someField: String) {

def concat(me: SomeField): String = someField + me

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 23: Real world gobbledygook

class MyClass(val someField: String) {

def concat(me: SomeField): String = someField + me

def hasCode(): String = { .. }

def equals(me: Object): Boolean = { .. }

}

Page 24: Real world gobbledygook

case class MyClass(someField: String) {

def concat(me: SomeField): String = someField + me

}

Page 25: Real world gobbledygook

case class MyClass(someField: String) {

def concat(me: SomeField): String = someField + me

}

Page 26: Real world gobbledygook

Real-World Gobbledygook

What is all the fuss with all those monoids and semigroups I keep hearing about?

Page 27: Real world gobbledygook

“But what if I don’t care about Functional Programming?”

Page 28: Real world gobbledygook

“Functional Programming is merely an academic phenomenon.

Page 29: Real world gobbledygook

Why not FP?

Page 30: Real world gobbledygook

Why not FP?

● FP might be good nice academic exercise, but ..

Page 31: Real world gobbledygook

Why not FP?

● FP might be good nice academic exercise, but ..● .. has nothing to do with real world

Page 32: Real world gobbledygook

Why not FP?

● FP might be good nice academic exercise, but ..● .. has nothing to do with real world● The last three decades proved that OO-programming is

useful & necessary

Page 33: Real world gobbledygook

Why not FP?

● FP might be good nice academic exercise, but ..● .. has nothing to do with real world● The last three decades proved that OO-programming is

useful & necessary ● OO has some inconveniences, but it is a de facto standard

Page 34: Real world gobbledygook

Why not FP?

● FP might be good nice academic exercise, but ..● .. has nothing to do with real world● The last three decades proved that OO-programming is

useful & necessary ● OO has some inconveniences, but it is a de facto standard● FP is complex, I don’t understand it, I doubt it will be

applicable to real world, ever!

Page 35: Real world gobbledygook
Page 36: Real world gobbledygook

Edsger Dijkstra

Page 37: Real world gobbledygook

Edsger Dijkstraˈɛtsxər ˈdɛɪkstra

Page 38: Real world gobbledygook

Edsger Dijkstraˈɛtsxər ˈdɛɪkstra

https://en.wikipedia.org/wiki/Edsger_W._Dijkstra

Page 39: Real world gobbledygook
Page 40: Real world gobbledygook
Page 41: Real world gobbledygook
Page 42: Real world gobbledygook
Page 43: Real world gobbledygook
Page 44: Real world gobbledygook

https://xkcd.com/292/

Page 45: Real world gobbledygook
Page 46: Real world gobbledygook
Page 47: Real world gobbledygook

This paper tries to convince us that the well-known goto statement should be eliminated from our programming languages or, at least (since I don’t think that it will ever be eliminated), that programmers should not use it. (...)

Page 48: Real world gobbledygook

This paper tries to convince us that the well-known goto statement should be eliminated from our programming languages or, at least (since I don’t think that it will ever be eliminated), that programmers should not use it. (...) The author is a proponent of the socalled “structured programming” style, in which, if I get it right, gotos are replaced by indentation.

Page 49: Real world gobbledygook

This paper tries to convince us that the well-known goto statement should be eliminated from our programming languages or, at least (since I don’t think that it will ever be eliminated), that programmers should not use it. (...) The author is a proponent of the socalled “structured programming” style, in which, if I get it right, gotos are replaced by indentation. Structured programming is a nice academic exercise, which works well for small examples, but I doubt that any real-world program will ever be written in such a style.

Page 50: Real world gobbledygook

This paper tries to convince us that the well-known goto statement should be eliminated from our programming languages or, at least (since I don’t think that it will ever be eliminated), that programmers should not use it. (...) The author is a proponent of the socalled “structured programming” style, in which, if I get it right, gotos are replaced by indentation. Structured programming is a nice academic exercise, which works well for small examples, but I doubt that any real-world program will ever be written in such a style. More than 10 years of industrial experience with Fortran have proved conclusively to everybody concerned that, in the real world, the goto is useful and necessary:

Page 51: Real world gobbledygook

This paper tries to convince us that the well-known goto statement should be eliminated from our programming languages or, at least (since I don’t think that it will ever be eliminated), that programmers should not use it. (...) The author is a proponent of the socalled “structured programming” style, in which, if I get it right, gotos are replaced by indentation. Structured programming is a nice academic exercise, which works well for small examples, but I doubt that any real-world program will ever be written in such a style. More than 10 years of industrial experience with Fortran have proved conclusively to everybody concerned that, in the real world, the goto is useful and necessary: its presence might cause some inconveniences in debugging, but it is a de facto standard and we must live with it.

Page 52: Real world gobbledygook

This paper tries to convince us that the well-known goto statement should be eliminated from our programming languages or, at least (since I don’t think that it will ever be eliminated), that programmers should not use it. (...) The author is a proponent of the socalled “structured programming” style, in which, if I get it right, gotos are replaced by indentation. Structured programming is a nice academic exercise, which works well for small examples, but I doubt that any real-world program will ever be written in such a style. More than 10 years of industrial experience with Fortran have proved conclusively to everybody concerned that, in the real world, the goto is useful and necessary: its presence might cause some inconveniences in debugging, but it is a de facto standard and we must live with it. It will take more than the academic elucubrations of a purist to remove it from our languages. (...)

Page 53: Real world gobbledygook
Page 54: Real world gobbledygook

“The Story of a Blackout”

In 5 acts

Page 55: Real world gobbledygook

Act.1: “Loss & Grief”

Page 56: Real world gobbledygook
Page 57: Real world gobbledygook

Our goal:

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

Page 58: Real world gobbledygook
Page 59: Real world gobbledygook
Page 60: Real world gobbledygook
Page 61: Real world gobbledygook
Page 62: Real world gobbledygook

503 Service Temporarily Unavailablenginx

Page 63: Real world gobbledygook

503 Service Temporarily Unavailablenginx

Page 64: Real world gobbledygook
Page 65: Real world gobbledygook
Page 66: Real world gobbledygook

The 5 Stages of Loss and Grief

Page 67: Real world gobbledygook

The 5 Stages of Loss and Grief

1. Denial

Page 68: Real world gobbledygook

The 5 Stages of Loss and Grief

1. Denial2. Anger

Page 69: Real world gobbledygook

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining

Page 70: Real world gobbledygook

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining4. Depression

Page 71: Real world gobbledygook

The 5 Stages of Loss and Grief

1. Denial2. Anger3. Bargaining4. Depression5. Acceptance

Page 72: Real world gobbledygook
Page 73: Real world gobbledygook

Act.2: “The Mess”

Page 74: Real world gobbledygook

auction

Page 75: Real world gobbledygook

auctionify

Page 76: Real world gobbledygook

auctionify.io

Page 77: Real world gobbledygook

Domain & Feature RequestsDomain:

● Users place Orders in the auction system

Order

Page 78: Real world gobbledygook

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 79: Real world gobbledygook

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 80: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 81: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 82: Real world gobbledygook

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 83: Real world gobbledygook

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 84: Real world gobbledygook

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 85: Real world gobbledygook
Page 86: Real world gobbledygook
Page 87: Real world gobbledygook
Page 88: Real world gobbledygook
Page 89: Real world gobbledygook
Page 90: Real world gobbledygook
Page 91: Real world gobbledygook

Subtype Polymorphism

Page 92: Real world gobbledygook

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 93: Real world gobbledygook

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 94: Real world gobbledygook

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 95: Real world gobbledygook

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 96: Real world gobbledygook

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 97: Real world gobbledygook

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 98: Real world gobbledygook

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 99: Real world gobbledygook
Page 100: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 101: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 102: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 103: Real world gobbledygook

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 104: Real world gobbledygook

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 105: Real world gobbledygook

object Stat {

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

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

}

Page 106: Real world gobbledygook

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 107: Real world gobbledygook
Page 108: Real world gobbledygook

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

more generic?

Page 109: Real world gobbledygook

object Stat {

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

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

}

Page 110: Real world gobbledygook

object Stat {

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

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

}

Page 111: Real world gobbledygook
Page 112: Real world gobbledygook
Page 113: Real world gobbledygook

trait Number[A] {

def value: A

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

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

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

}

Page 114: Real world gobbledygook

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 115: Real world gobbledygook

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 116: Real world gobbledygook

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 117: Real world gobbledygook

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 118: Real world gobbledygook

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 119: Real world gobbledygook

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 120: Real world gobbledygook

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 121: Real world gobbledygook

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 122: Real world gobbledygook
Page 123: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 124: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 125: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 126: Real world gobbledygook

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 127: Real world gobbledygook

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 128: Real world gobbledygook

object JsonSerializer {

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

}

Page 129: Real world gobbledygook
Page 130: Real world gobbledygook
Page 131: Real world gobbledygook

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

your code a bit more generic? Thanks!!!

Page 132: Real world gobbledygook

object JsonSerializer {

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

}

Page 133: Real world gobbledygook

object JsonSerializer {

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

}

Page 134: Real world gobbledygook

object JsonSerializer {

def write(serializable: JsonSerializable) = ...

}

Page 135: Real world gobbledygook

object JsonSerializer {

def write(serializable: JsonSerializable) = ...

}

trait JsonSerializable {

def toJson: JsonValue

}

Page 136: Real world gobbledygook

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

trait JsonSerializable {

def toJson: JsonValue

}

Page 137: Real world gobbledygook

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

trait JsonSerializable {

def toJson: JsonValue

}

Page 138: Real world gobbledygook

sealed trait JsonValue

Page 139: Real world gobbledygook

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 140: Real world gobbledygook

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 141: Real world gobbledygook

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 {

Page 142: Real world gobbledygook

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 JsonNumber(value) => value.toString

}

}

Page 143: Real world gobbledygook

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 JsonString(value) => s""""$value""""

case JsonNumber(value) => value.toString

}

}

Page 144: Real world gobbledygook

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 JsonArray(elems) => "[" + elems.map(write).mkString(", ") + "]"

case JsonString(value) => s""""$value""""

case JsonNumber(value) => value.toString

}

}

Page 145: Real world gobbledygook

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 146: Real world gobbledygook

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 147: Real world gobbledygook

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 148: Real world gobbledygook

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable {

def evaluate: BigDecimal

}

Page 149: Real world gobbledygook

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable {

def evaluate: BigDecimal

}

case class GeneralOrder(products: List[Product]) extends Order {

def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {

case (acc, p) => acc + p.evaluate

}

def toJson: JsonValue = JsonObject(Map(

"type" -> JsonString("general"),

"products" -> JsonArray(products.map(_.toJson))

))

}

Page 150: Real world gobbledygook

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable {

def evaluate: BigDecimal

}

case object CancelledOrder extends Order {

def evaluate: BigDecimal = BigDecimal("0.0")

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

}

Page 151: Real world gobbledygook

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable {

def evaluate: BigDecimal

}

case class ComplexOrder(orders: List[Order]) extends Order {

def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) {

case (acc, o) => acc + o.evaluate

}

override def toJson: JsonValue = JsonObject(Map(

"type" -> JsonString("complex"),

"orders" -> JsonArray(orders.map(_.toJson)))

)

}

Page 152: Real world gobbledygook

sealed trait Product extends Evaluatable[BigDecimal] with JsonSerializable {

def evaluate: BigDecimal

}

case class BasicProduct(id: Int, price: BigDecimal) extends Product {

def evaluate: BigDecimal = price

def toJson: JsonValue = JsonObject(Map(

"type" -> JsonString("basic"),

"id" -> JsonNumber(BigDecimal(id)),

"price" -> JsonNumber(price)

))

}

Page 153: Real world gobbledygook

sealed trait Product extends Evaluatable[BigDecimal] with JsonSerializable {

def evaluate: BigDecimal

}

case class DiscountedProduct(product: Product, discount: Double) extends Product {

def evaluate: BigDecimal = product.evaluate * (1 - discount)

def toJson: JsonValue = JsonObject(Map(

"type" -> JsonString("discounted"),

"product" -> product.toJson,

"discount" -> JsonNumber(discount)

))

}

Page 154: Real world gobbledygook

sealed trait Product extends Evaluatable[BigDecimal] with JsonSerializable {

def evaluate: BigDecimal

}

case object OutOfStock extends Product {

def evaluate: BigDecimal = BigDecimal("0.0")

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

}

Page 155: Real world gobbledygook

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 156: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 157: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 158: Real world gobbledygook
Page 159: Real world gobbledygook
Page 160: Real world gobbledygook
Page 161: Real world gobbledygook
Page 162: Real world gobbledygook

Our goal:

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

Page 163: Real world gobbledygook

Act.3: “Re-inventing the Wheel”

Page 164: Real world gobbledygook

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 165: Real world gobbledygook

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 166: Real world gobbledygook

object Order {

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

Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value

}

Page 167: Real world gobbledygook

object Order {

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

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

}

Page 168: Real world gobbledygook

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 169: Real world gobbledygook

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 170: Real world gobbledygook

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 171: Real world gobbledygook

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 172: Real world gobbledygook

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 173: Real world gobbledygook

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 174: Real world gobbledygook

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 175: Real world gobbledygook

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 176: Real world gobbledygook

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 177: Real world gobbledygook

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 178: Real world gobbledygook

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 179: Real world gobbledygook

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 180: Real world gobbledygook

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 181: Real world gobbledygook

[info] OrderTest:

[info] - should evaluate order

[info] - should calculate average

[info] - should serialize to json

Page 182: Real world gobbledygook
Page 183: Real world gobbledygook
Page 184: Real world gobbledygook

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 185: Real world gobbledygook

sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable

case class GeneralOrder(products: List[Product]) extends Order {

def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {

case (acc, p) => acc + p.evaluate

}

def toJson: JsonValue = JsonObject(Map(

"type" -> JsonString("general"),

"products" -> JsonArray(products.map(_.toJson))

))

}

Page 186: Real world gobbledygook

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 187: Real world gobbledygook

trait JsonSerializable {

def toJson: JsonValue

}

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

Page 188: Real world gobbledygook

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

Page 189: Real world gobbledygook

trait Json[-A] {

def toJson(a: A): JsonValue

}

object JsonSerializer {

def write(serializable: JsonSerializable) =

JsonWriter.write(serializable.toJson)

}

Page 190: Real world gobbledygook

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 191: Real world gobbledygook

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 192: Real world gobbledygook

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 193: Real world gobbledygook

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 194: Real world gobbledygook

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 195: Real world gobbledygook

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 196: Real world gobbledygook

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 could not find implicit value for parameter json: json.Json[jw.

ComplexOrder]

[error] JsonSerializer.write(order) should equal(expectedJson)

Page 197: Real world gobbledygook

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 198: Real world gobbledygook

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 199: Real world gobbledygook

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 200: Real world gobbledygook
Page 201: Real world gobbledygook

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 class OutOfStock() extends Product {

def evaluate: BigDecimal = BigDecimal("0.0")

}

Page 202: Real world gobbledygook

sealed trait Order extends Evaluatable[BigDecimal]

case class GeneralOrder(products: List[Product]) extends Order {

def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {

case (acc, p) => acc + p.evaluate

}

}

case class 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

}

Page 203: Real world gobbledygook

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 204: Real world gobbledygook

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 205: Real world gobbledygook

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

object Order {

import Number._

def average(orders: Seq[Order])(implicit ev: Evaluate[Order, BigDecimal]: BigDecimal =

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

}

Page 206: Real world gobbledygook

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

object Order {

import Number._

def average(orders: Seq[Order])(implicit ev: Evaluate[Order, BigDecimal]: BigDecimal =

Stat.mean(orders.map(o => ev.evaluate(o))

}

Page 207: Real world gobbledygook
Page 208: Real world gobbledygook

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 209: Real world gobbledygook

Act.4: “Renaissance”

Page 210: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 211: Real world gobbledygook

Domain & Feature RequestsFeature Requests:

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

Page 212: Real world gobbledygook

def simplify(orders: Seq[Order]): Order =

orders.foldLeft(CancelledOrder()) { ?? }

Page 213: Real world gobbledygook

def simplify(orders: Seq[Order]): Order =

orders.foldLeft(CancelledOrder()) {

case (acc, o) => acc + o

}

Page 214: Real world gobbledygook

def simplify(orders: Seq[Order]): Order =

orders.foldLeft(CancelledOrder()) {

case (acc, o) => acc + o

}

trait Addable[A] {

def add(a1: A, a2: A): A

}

Page 215: Real world gobbledygook

def simplify(orders: Seq[Order]): Order =

orders.foldLeft(CancelledOrder()) {

case (acc, o) => acc + o

}

trait Addable[A] {

def add(a1: A, a2: A): A

}

implicit object OrderAddable extends Addable[Order] {

def add(o1: Order, o2: Order): Order = (o1, o2) match {

case (CancelledOrder(), o2) => o2

case (o1, CancelledOrder()) => o1

case (GeneralOrder(ps1), GeneralOrder(ps2)) => GeneralOrder(ps1 ++ ps2)

case (ComplexOrder(os1), ComplexOrder(os2)) => ComplexOrder(os1 ++ os2)

case (o1, ComplexOrder(orders)) => ComplexOrder(o1 :: orders)

case (ComplexOrder(orders), o2) => ComplexOrder(o2 :: orders)

}

}

Page 216: Real world gobbledygook

def simplify(orders: Seq[Order])(implicit addable: Addable[Order]): Order =

orders.foldLeft(CancelledOrder()) {

case (acc, o) => addable.add(acc, o)

}

trait Addable[A] {

def add(a1: A, a2: A): A

}

implicit object OrderAddable extends Addable[Order] {

def add(o1: Order, o2: Order): Order = (o1, o2) match {

case (CancelledOrder(), o2) => o2

case (o1, CancelledOrder()) => o1

case (GeneralOrder(ps1), GeneralOrder(ps2)) => GeneralOrder(ps1 ++ ps2)

case (ComplexOrder(os1), ComplexOrder(os2)) => ComplexOrder(os1 ++ os2)

case (o1, ComplexOrder(orders)) => ComplexOrder(o1 :: orders)

case (ComplexOrder(orders), o2) => ComplexOrder(o2 :: orders)

}

}

Page 217: Real world gobbledygook

def simplify(orders: Seq[Order])(implicit addable: Addable[Order]): Order =

orders.foldLeft(CancelledOrder()) {

case (acc, o) => addable.add(acc, o)

}

Page 218: Real world gobbledygook

def simplify(orders: Seq[Order]): Order = fold(orders)

Page 219: Real world gobbledygook

def simplify(orders: Seq[Order])(implicit addable: Addable[Order]): Order =

orders.foldLeft(CancelledOrder()) {

case (acc, o) => addable.add(acc, o)

}

Page 220: Real world gobbledygook

def simplify(orders: Seq[Order])(implicit addable: Addable[Order]): Order =

orders.foldLeft(CancelledOrder()) {

case (acc, o) => addable.add(acc, o)

}

Page 221: Real world gobbledygook

def simplify(orders: Seq[Order]): Order = fold(orders)

trait AddableWithZero[A] {

def zero: A

def add(a1: A, a2: A): A

}

Page 222: Real world gobbledygook

def simplify(orders: Seq[Order]): Order = fold(orders)

trait AddableWithZero[A] {

def zero: A

def add(a1: A, a2: A): A

}

object AddableWithZero {

def fold[A](values: Seq[A])(implicit addableWithZ: AddableWithZero[A]): A = {

values.fold(addableWithZ.zero){

case (acc, v) => addableWithZ.add(acc, v)

}

}

}

Page 223: Real world gobbledygook

def simplify(orders: Seq[Order]): Order = fold(orders)

trait AddableWithZero[A] {

def zero: A

def add(a1: A, a2: A): A

}

Page 224: Real world gobbledygook

def simplify(orders: Seq[Order]): Order = fold(orders)

trait AddableWithZero[A] {

def zero: A

def add(a1: A, a2: A): A

}

implicit object OrderAddableWithZero extends AddableWithZero[Order] {

def zero = CancelledOrder()

def add(o1: Order, o2: Order): Order = OrderAddable.add(o1,o2)

}

Page 225: Real world gobbledygook

def simplify(orders: Seq[Order]): Order = fold(orders)

trait AddableWithZero[A] {

def zero: A

def add(a1: A, a2: A): A

}

object AddableWithZero {

def fold[A](values: Seq[A])(implicit addableWithZ: AddableWithZero[A]): A = {

values.fold(addableWithZ.zero){

case (acc, v) => addableWithZ.add(acc, v)

}

}

}

Page 226: Real world gobbledygook
Page 227: Real world gobbledygook

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 228: Real world gobbledygook

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 229: Real world gobbledygook
Page 230: Real world gobbledygook

Act.5: “Enlightenment”

Page 231: Real world gobbledygook

Research

Page 232: Real world gobbledygook

Definitions

Page 233: Real world gobbledygook

Definitions● Type classes

Page 234: Real world gobbledygook

Definitions● Type classes

○ Ad-hoc polymorphism

Page 235: Real world gobbledygook

Definitions● Type classes● Abstract Data Type (ADT)

Page 236: Real world gobbledygook

Definitions● Type classes● Abstract Data Type (ADT)● Addable → Semigroup

Page 237: Real world gobbledygook

Definitions● Type classes● Abstract Data Type (ADT)● Addable → Semigroup● AddableWithZero → Monoid

Page 238: Real world gobbledygook

Definitions● Type classes● Abstract Data Type (ADT)● Addable → Semigroup● AddableWithZero → Monoid● Type classes come with laws!

Page 239: Real world gobbledygook

Where to go next1. Functor2. Applicative3. Monad!

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

Page 240: Real world gobbledygook

Before we go, a bit of history...

Page 241: Real world gobbledygook
Page 242: Real world gobbledygook

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 243: Real world gobbledygook

And that’s all folks!

Page 244: Real world gobbledygook

And that’s all folks!

Paweł Szulc

Page 245: Real world gobbledygook

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

Page 246: Real world gobbledygook

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

Page 247: Real world gobbledygook

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

github: https://github.com/rabbitonweb

Page 248: Real world gobbledygook

And that’s all folks!

Paweł Szulc twitter: @rabbitonweb

blog: http://rabbitonweb.com

github: https://github.com/rabbitonweb

Questions?

Page 249: Real world gobbledygook

Thank you!