Upload
pawel-szulc
View
392
Download
0
Embed Size (px)
Citation preview
Real-World Gobbledygook
What is all the fuss with all those monoids and semigroups I keep hearing about?
Scala?
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) { .. }
}
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) { .. }
}
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) { .. }
}
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) { .. }
}
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) { .. }
}
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) { .. }
}
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) { .. }
}
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 = { .. }
}
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 = { .. }
}
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 = { .. }
}
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 = { .. }
}
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 = { .. }
}
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 = { .. }
}
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 = { .. }
}
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 = { .. }
}
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 = { .. }
}
class MyClass(someField: String) {
def someField(): String = someField
def concat(me: SomeField): String = someField + me
def hasCode(): String = { .. }
def equals(me: Object): Boolean = { .. }
}
class MyClass(someField: String) {
def someField(): String = someField
def concat(me: SomeField): String = someField + me
def hasCode(): String = { .. }
def equals(me: Object): Boolean = { .. }
}
class MyClass(val someField: String) {
def concat(me: SomeField): String = someField + me
def hasCode(): String = { .. }
def equals(me: Object): Boolean = { .. }
}
class MyClass(val someField: String) {
def concat(me: SomeField): String = someField + me
def hasCode(): String = { .. }
def equals(me: Object): Boolean = { .. }
}
class MyClass(val someField: String) {
def concat(me: SomeField): String = someField + me
def hasCode(): String = { .. }
def equals(me: Object): Boolean = { .. }
}
case class MyClass(someField: String) {
def concat(me: SomeField): String = someField + me
}
case class MyClass(someField: String) {
def concat(me: SomeField): String = someField + me
}
Real-World Gobbledygook
What is all the fuss with all those monoids and semigroups I keep hearing about?
“But what if I don’t care about Functional Programming?”
“Functional Programming is merely an academic phenomenon.
Why not FP?
Why not FP?
● FP might be good nice academic exercise, but ..
Why not FP?
● FP might be good nice academic exercise, but ..● .. has nothing to do with real world
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
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
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!
Edsger Dijkstra
Edsger Dijkstraˈɛtsxər ˈdɛɪkstra
Edsger Dijkstraˈɛtsxər ˈdɛɪkstra
https://en.wikipedia.org/wiki/Edsger_W._Dijkstra
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. (...)
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.
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.
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:
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.
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. (...)
“The Story of a Blackout”
In 5 acts
Act.1: “Loss & Grief”
Our goal:
● DRY principle● Separation of concerns● Epic decoupling● Clean API● Minimal Boilerplate
503 Service Temporarily Unavailablenginx
503 Service Temporarily Unavailablenginx
The 5 Stages of Loss and Grief
The 5 Stages of Loss and Grief
1. Denial
The 5 Stages of Loss and Grief
1. Denial2. Anger
The 5 Stages of Loss and Grief
1. Denial2. Anger3. Bargaining
The 5 Stages of Loss and Grief
1. Denial2. Anger3. Bargaining4. Depression
The 5 Stages of Loss and Grief
1. Denial2. Anger3. Bargaining4. Depression5. Acceptance
Act.2: “The Mess”
auction
auctionify
auctionify.io
Domain & Feature RequestsDomain:
● Users place Orders in the auction system
Order
Domain & Feature RequestsDomain:
● Users place Orders in the auction system● Orders can be:
○ General - contain list of Products○ Complex - combined from two or more
other Orders○ Cancelled - used to be fully fledged
Orders, but now are cancelled
Order
General Complex Cancelled
Domain & Feature RequestsDomain:
● Users place Orders in the auction system● Orders can be:
○ General - contain list of Products○ Complex - combined from two or more
other Orders○ Cancelled - used to be fully fledged
Orders, but now are cancelled● Products can be:
○ Basic - id & price○ Discounted - wrapped Product &
discount○ OutOfStock - used to be in warehouse, but
now gone (sorry)
Order
General Complex Cancelled
Product
Basic Discounter Out of Stock
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
sealed trait Order
case class GeneralOrder(products: List[Product]) extends Order
case object CancelledOrder extends Order
case class ComplexOrder(orders: List[Order]) extends Order
sealed trait Product
case class BasicProduct(id: Int, price: BigDecimal) extends Product
case class DiscountedProduct(product: Product,
discount: Double) extends Product
case object OutOfStock extends Product
test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
[error] OrderTest.scala evaluate is not a member of jw.ComplexOrder
[error] order.evaluate should equal (BigDecimal("11.0"))
[error] ^
[error] one error found
Subtype Polymorphism
test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
[error] OrderTest.scala evaluate is not a member of jw.ComplexOrder
[error] order.evaluate should equal (BigDecimal("11.0"))
[error] ^
[error] one error found
test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
sealed trait Order
case class GeneralOrder(products: List[Product]) extends Order
case object CancelledOrder extends Order
case class ComplexOrder(orders: List[Order]) extends Order
sealed trait Product
case class BasicProduct(id: Int, price: BigDecimal) extends Product
case class DiscountedProduct(product: Product,
discount: Double) extends Product
case object OutOfStock extends Product
trait Evaluatable[T] { def evaluate: T }
sealed trait Order extends Evaluatable[BigDecimal]
case object CancelledOrder extends Order {
def evaluate: BigDecimal = BigDecimal("0.0")
}
case class ComplexOrder(orders: List[Order]) extends Order {
def evaluate: BigDecimal = orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + o.evaluate
}
}
case class GeneralOrder(products: List[Product]) extends Order {
def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + p.evaluate
}
}
trait Evaluatable[T] { def evaluate: T }
sealed trait Product extends Evaluatable[BigDecimal]
case class BasicProduct(id: Int, price: BigDecimal) extends Product {
def evaluate: BigDecimal = price
}
case class DiscountedProduct(product: Product, discount: Double) extends Product {
def evaluate: BigDecimal = product.evaluate * (1 - discount)
}
case object OutOfStock extends Product {
def evaluate: BigDecimal = BigDecimal("0.0")
}
test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
test("should evaluate order") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = GeneralOrder(DiscountedProduct(
product = BasicProduct(11, BigDecimal("1")),
discount = 0.2) :: Nil)
val o3 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: o3 :: Nil)
order.evaluate should equal (BigDecimal("11.0"))
}
[info] OrderTest:
[info] - should evaluate order
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
test("should calculate average") {
val o1 = GeneralOrder(DiscountedProduct(product =
BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil)
val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil)
val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0"))
}
test("should calculate average") {
val o1 = GeneralOrder(DiscountedProduct(product =
BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil)
val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil)
val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0"))
}
[error] OrderTest.scala average is not a member of jw.Order
[error] Order.average should equal (BigDecimal("5.0"))
[error] ^
[error] one error found
object Stat {
def mean(xs: Seq[BigDecimal]): BigDecimal =
xs.reduce(_ + _) / xs.size
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
object Stat {
def mean(xs: Seq[BigDecimal]): BigDecimal =
xs.reduce(_ + _) / xs.size
}
AWESOME!I just need it to work for Doubles and Ints as well! Can you make it
more generic?
object Stat {
def mean(xs: Seq[BigDecimal]): BigDecimal =
xs.reduce(_ + _) / xs.size
}
object Stat {
def mean(xs: Seq[Number]): Number =
xs.reduce(_ + _) / xs.size
}
trait Number[A] {
def value: A
def +(other: Number[A]): Number[A]
def /(other: Number[A]): Number[A]
def /(other: Int): Number[A]
}
trait Number[A] {
def value: A
def +(other: Number[A]): Number[A]
def /(other: Number[A]): Number[A]
def /(other: Int): Number[A]
}
case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] {
def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value)
def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value)
def /(other: Int) = BigDecimalNumber(value / other)
}
trait Number[A] {
def value: A
def +(other: Number[A]): Number[A]
def /(other: Number[A]): Number[A]
def /(other: Int): Number[A]
}
case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] {
def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value)
def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value)
def /(other: Int) = BigDecimalNumber(value / other)
}
trait Number[A] {
def value: A
def +(other: Number[A]): Number[A]
def /(other: Number[A]): Number[A]
def /(other: Int): Number[A]
}
case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] {
def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value)
def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value)
def /(other: Int) = BigDecimalNumber(value / other)
}
case class IntNumber(value: Int) extends Number[Int] {
def +(other: Number[Int]) = IntNumber(value + other.value)
def /(other: Number[Int]) = IntNumber(value / other.value)
def /(other: Int) = IntNumber(value / other)
}
trait Number[A] {
def value: A
def +(other: Number[A]): Number[A]
def /(other: Number[A]): Number[A]
def /(other: Int): Number[A]
}
case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] {
def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value)
def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value)
def /(other: Int) = BigDecimalNumber(value / other)
}
case class IntNumber(value: Int) extends Number[Int] {
def +(other: Number[Int]) = IntNumber(value + other.value)
def /(other: Number[Int]) = IntNumber(value / other.value)
def /(other: Int) = IntNumber(value / other)
}
case class DoubleNumber(value: Double) extends Number[Double] {
def +(other: Number[Double]) = DoubleNumber(value + other.value)
def /(other: Number[Double]) = DoubleNumber(value / other.value)
def /(other: Int) = DoubleNumber(value / other)
}
object Stat {
def mean(xs: Seq[BigDecimal]): BigDecimal =
xs.reduce(_ + _) / xs.size
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
object Stat {
def mean[A](xs: Seq[Number[A]]): Number[A] =
xs.reduce(_ + _) / xs.size
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value
}
object Stat {
def mean[A](xs: Seq[Number[A]]): Number[A] =
xs.reduce(_ + _) / xs.size
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value
}
test("should calculate average") {
val o1 = GeneralOrder(DiscountedProduct(product =
BasicProduct(11, BigDecimal("1")), discount = 0.2) :: Nil)
val o2 = GeneralOrder(BasicProduct(10, BigDecimal("4")) :: Nil)
val o3 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
Order.average(o1 :: o2 :: o3 :: Nil) should equal(BigDecimal("5.0"))
}
[info] OrderTest:
[info] - should evaluate order
[info] - should calculate average
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
[error] OrderTest.scala not found: value JsonSerializer
[error] JsonSerializer.write(order) should equal(expectedJson)
[error] ^
object JsonSerializer {
def write(order: Order): String = ...
}
I have few objects I also need to serialize to JSON. Can you make
your code a bit more generic? Thanks!!!
object JsonSerializer {
def write(order: Order): String = ...
}
object JsonSerializer {
def write(sth: ???): String = ...
}
object JsonSerializer {
def write(serializable: JsonSerializable) = ...
}
object JsonSerializer {
def write(serializable: JsonSerializable) = ...
}
trait JsonSerializable {
def toJson: JsonValue
}
object JsonSerializer {
def write(serializable: JsonSerializable) =
JsonWriter.write(serializable.toJson)
}
trait JsonSerializable {
def toJson: JsonValue
}
object JsonSerializer {
def write(serializable: JsonSerializable) =
JsonWriter.write(serializable.toJson)
}
trait JsonSerializable {
def toJson: JsonValue
}
sealed trait JsonValue
sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String =
sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String = json match {
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
}
}
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
}
}
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
}
}
sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String = json match {
case JsonObject(elems) =>
val entries = for {
(key, value) <- elems
} yield s""""$key: ${write(value)}""""
"{" + entries.mkString(", ") + "}"
case JsonArray(elems) => "[" + elems.map(write).mkString(", ") + "]"
case JsonString(value) => s""""$value""""
case JsonNumber(value) => value.toString
}
}
sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String = ...
}
sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String = ...
}
trait JsonSerializable {
def toJson: JsonValue
}
object JsonSerializer {
def write(serializable: JsonSerializable) =
JsonWriter.write(serializable.toJson)
}
sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable {
def evaluate: BigDecimal
}
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))
))
}
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")
}
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)))
)
}
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)
))
}
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)
))
}
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")
}
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
[info] OrderTest:
[info] - should evaluate order
[info] - should calculate average
[info] - should serialize to json
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
Our goal:
● DRY principle● Separation of concerns● Epic decoupling● Clean API● Minimal Boilerplate
Act.3: “Re-inventing the Wheel”
trait Number[A] {
def value: A
def +(other: Number[A]): Number[A]
def /(other: Number[A]): Number[A]
def /(other: Int): Number[A]
}
case class BigDecimalNumber(value: BigDecimal) extends Number[BigDecimal] {
def +(other: Number[BigDecimal]) = BigDecimalNumber(value + other.value)
def /(other: Number[BigDecimal]) = BigDecimalNumber(value / other.value)
def /(other: Int) = BigDecimalNumber(value / other)
}
case class IntNumber(value: Int) extends Number[Int] {
def +(other: Number[Int]) = IntNumber(value + other.value)
def /(other: Number[Int]) = IntNumber(value / other.value)
def /(other: Int) = IntNumber(value / other)
}
case class DoubleNumber(value: Double) extends Number[Double] {
def +(other: Number[Double]) = DoubleNumber(value + other.value)
def /(other: Number[Double]) = DoubleNumber(value / other.value)
def /(other: Int) = DoubleNumber(value / other)
}
object Stat {
def mean[A](xs: Seq[Number[A]]): Number[A] =
xs.reduce(_ + _) / xs.size
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(o => BigDecimalNumber(o.evaluate))).value
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
object Stat {
def mean[A](xs: Seq[Number[A]]): Number[A] =
xs.reduce(_ + _) / xs.size
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
object Stat {
def mean[A](xs: Seq[A]): A =
xs.reduce(_ + _) / xs.size
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
object Stat {
def mean[A](xs: Seq[A])(implicit number: Number[A]): A =
number.divide(xs.reduce(number.plus),xs.size)
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
trait Number[A] {
def plus(n1: A, n2: A): A
def divide(n1: A, n2: A): A
def divide(n1: A, n2: Int): A
}
trait Number[A] {
def plus(n1: A, n2: A): A
def divide(n1: A, n2: A): A
def divide(n1: A, n2: Int): A
}
object Number {
implicit object BigDecimalNumber extends Number[BigDecimal] {
def plus(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 + n2
def divide(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 / n2
def divide(n1: BigDecimal, n2: Int): BigDecimal = n1 / n2
}
trait Number[A] {
def plus(n1: A, n2: A): A
def divide(n1: A, n2: A): A
def divide(n1: A, n2: Int): A
}
object Number {
implicit object BigDecimalNumber extends Number[BigDecimal] {
def plus(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 + n2
def divide(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 / n2
def divide(n1: BigDecimal, n2: Int): BigDecimal = n1 / n2
}
implicit object IntNumber extends Number[Int] {
def plus(n1: Int, n2: Int): Int = n1 + n2
def divide(n1: Int, n2: Int): Int = n1 / n2
}
}
object Stat {
def mean[A](xs: Seq[A])(implicit number: Number[A]): A =
number.divide(xs.reduce(number.plus),xs.size)
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
object Stat {
def mean[A](xs: Seq[A])(implicit number: Number[A]): A =
number.divide(xs.reduce(number.plus),xs.size)
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))(BigDecimalNumber)
}
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
}
}
trait Number[A] {
def plus(n1: A, n2: A): A
def divide(n1: A, n2: A): A
def divide(n1: A, n2: Int): A
}
object Number {
implicit object BigDecimalNumber extends Number[BigDecimal] {
def plus(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 + n2
def divide(n1: BigDecimal, n2: BigDecimal): BigDecimal = n1 / n2
def divide(n1: BigDecimal, n2: Int): BigDecimal = n1 / n2
}
implicit object IntNumber extends Number[Int] {
def plus(n1: Int, n2: Int): Int = n1 + n2
def divide(n1: Int, n2: Int): Int = n1 / n2
}
}
object Stat {
def mean[A](xs: Seq[A])(implicit number: Number[A]): A =
number.divide(xs.reduce(number.plus),xs.size)
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))(BigDecimalNumber)
}
object Stat {
def mean[A](xs: Seq[A])(implicit number: Number[A]): A =
number.divide(xs.reduce(number.plus),xs.size)
}
object Order {
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
object Stat {
def mean[A](xs: Seq[A])(implicit number: Number[A]): A =
number.divide(xs.reduce(number.plus),xs.size)
}
object Order {
import Number._
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
[info] OrderTest:
[info] - should evaluate order
[info] - should calculate average
[info] - should serialize to json
sealed trait JsonValue
case class JsonObject(elem: Map[String, JsonValue]) extends JsonValue
case class JsonArray(elems: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: BigDecimal) extends JsonValue
object JsonWriter {
def write(json: JsonValue): String = ...
}
trait JsonSerializable {
def toJson: JsonValue
}
object JsonSerializer {
def write(serializable: JsonSerializable) =
JsonWriter.write(serializable.toJson)
}
sealed trait Order extends Evaluatable[BigDecimal] with JsonSerializable
case class GeneralOrder(products: List[Product]) extends Order {
def evaluate: BigDecimal = products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + p.evaluate
}
def toJson: JsonValue = JsonObject(Map(
"type" -> JsonString("general"),
"products" -> JsonArray(products.map(_.toJson))
))
}
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
trait JsonSerializable {
def toJson: JsonValue
}
object JsonSerializer {
def write(serializable: JsonSerializable) =
JsonWriter.write(serializable.toJson)
}
object JsonSerializer {
def write(serializable: JsonSerializable) =
JsonWriter.write(serializable.toJson)
}
trait Json[-A] {
def toJson(a: A): JsonValue
}
object JsonSerializer {
def write(serializable: JsonSerializable) =
JsonWriter.write(serializable.toJson)
}
trait Json[-A] {
def toJson(a: A): JsonValue
}
object JsonSerializer {
def write[A](a: A)(implicit json: Json[A]) =
JsonWriter.write(json.toJson(a))
}
object OrderJson {
implicit object ProductToJson extends Json[Product] {
def toJson(product: Product): JsonValue = ...
}
implicit object OrderToJson extends Json[Order] {
def toJson(order: Order): JsonValue = ...
}
}
implicit object ProductToJson extends Json[Product] {
def toJson(product: Product): JsonValue = product match {
case BasicProduct(id, price) =>
JsonObject(Map(
"type" -> JsonString("basic"),
"id" -> JsonNumber(BigDecimal(id)),
"price" -> JsonNumber(price)
))
case DiscountedProduct(product, discount) =>
JsonObject(Map(
"type" -> JsonString("discounted"),
"product" -> toJson(product),
"discount" -> JsonNumber(discount)
))
case OutOfStock() =>
JsonString("out of stock")
}
}
implicit object OrderToJson extends Json[Order] {
def toJson(order: Order): JsonValue = order match {
case GeneralOrder(products) =>
JsonObject(Map(
"type" -> JsonString("general"),
"products" -> JsonArray(products.map(ProductToJson.toJson))
))
case ComplexOrder(orders) =>
JsonObject(Map(
"type" -> JsonString("complex"),
"orders" -> JsonArray(orders.map(toJson))
))
case CancelledOrder() =>
JsonString("cancelled order")
}
}
implicit object OrderToJson extends Json[Order] {
def toJson(order: Order): JsonValue = order match {
case GeneralOrder(products) =>
JsonObject(Map(
"type" -> JsonString("general"),
"products" -> JsonArray(products.map(ProductToJson.toJson))
))
case ComplexOrder(orders) =>
JsonObject(Map(
"type" -> JsonString("complex"),
"orders" -> JsonArray(orders.map(toJson))
))
case CancelledOrder() =>
JsonString("cancelled order")
}
}
object OrderJson {
implicit object ProductToJson extends Json[Product] {
def toJson(product: Product): JsonValue = ...
}
implicit object OrderToJson extends Json[Order] {
def toJson(order: Order): JsonValue = ...
}
}
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
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)
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
JsonSerializer.write(order) should equal(expectedJson)
}
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
import OrderJson._
JsonSerializer.write(order) should equal(expectedJson)
}
test("should serialize to json") {
val o1 = GeneralOrder(BasicProduct(10, BigDecimal("10.2")) :: Nil)
val o2 = CancelledOrder
val order = ComplexOrder(o1 :: o2 :: Nil)
val expectedJson = """{
"type: "complex"",
"orders: [{
"type: "general"",
"products: [{"type: "basic"", "id: 10", "price: 10.2"}]"
},
"cancelled order"]"
}"""
import OrderJson._
JsonSerializer.write(order) should equal(expectedJson)
}
[info] OrderTest:
[info] - should evaluate order
[info] - should calculate average
[info] - should serialize to json
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")
}
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
}
trait Evaluate[-A, T] { def evaluate(a: A): T }
object Order {
implicit object ProductEvaluate extends Evaluate[Product, BigDecimal] {
def evaluate(product: Product): BigDecimal = product match {
case BasicProduct(id, price) => price
case DiscountedProduct(discounted, discount) => evaluate(discounted) * (1 - discount)
case OutOfStock() => BigDecimal("0.0")
}}
implicit object OrderEvaluate extends Evaluate[Order, BigDecimal] {
def evaluate(order: Order): BigDecimal = order match {
case GeneralOrder(products) => products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + ProductEvaluate.evaluate(p)
}
case ComplexOrder(orders) => orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + evaluate(o)
}
case CancelledOrder() => BigDecimal("0.0")
}}}
trait Evaluate[-A, T] { def evaluate(a: A): T }
object Order {
import Number._
def average(orders: Seq[Order]): BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
trait Evaluate[-A, T] { def evaluate(a: A): T }
object Order {
import Number._
def average(orders: Seq[Order])(implicit ev: Evaluate[Order, BigDecimal]: BigDecimal =
Stat.mean(orders.map(_.evaluate))
}
trait Evaluate[-A, T] { def evaluate(a: A): T }
object Order {
import Number._
def average(orders: Seq[Order])(implicit ev: Evaluate[Order, BigDecimal]: BigDecimal =
Stat.mean(orders.map(o => ev.evaluate(o))
}
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
Act.4: “Renaissance”
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
Domain & Feature RequestsFeature Requests:
● Present current state of orders (JSON)● Calculate final checkout● Calculate average Order price● “Simplify” Orders
def simplify(orders: Seq[Order]): Order =
orders.foldLeft(CancelledOrder()) { ?? }
def simplify(orders: Seq[Order]): Order =
orders.foldLeft(CancelledOrder()) {
case (acc, o) => acc + o
}
def simplify(orders: Seq[Order]): Order =
orders.foldLeft(CancelledOrder()) {
case (acc, o) => acc + o
}
trait Addable[A] {
def add(a1: A, a2: A): A
}
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)
}
}
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)
}
}
def simplify(orders: Seq[Order])(implicit addable: Addable[Order]): Order =
orders.foldLeft(CancelledOrder()) {
case (acc, o) => addable.add(acc, o)
}
def simplify(orders: Seq[Order]): Order = fold(orders)
def simplify(orders: Seq[Order])(implicit addable: Addable[Order]): Order =
orders.foldLeft(CancelledOrder()) {
case (acc, o) => addable.add(acc, o)
}
def simplify(orders: Seq[Order])(implicit addable: Addable[Order]): Order =
orders.foldLeft(CancelledOrder()) {
case (acc, o) => addable.add(acc, o)
}
def simplify(orders: Seq[Order]): Order = fold(orders)
trait AddableWithZero[A] {
def zero: A
def add(a1: A, a2: A): A
}
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)
}
}
}
def simplify(orders: Seq[Order]): Order = fold(orders)
trait AddableWithZero[A] {
def zero: A
def add(a1: A, a2: A): A
}
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)
}
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)
}
}
}
trait Evaluate[-A, T] { def evaluate(a: A): T }
object Order {
implicit object ProductEvaluate extends Evaluate[Product, BigDecimal] {
def evaluate(product: Product): BigDecimal = product match {
case BasicProduct(id, price) => price
case DiscountedProduct(discounted, discount) => evaluate(discounted) * (1 - discount)
case OutOfStock() => BigDecimal("0.0")
}}
implicit object OrderEvaluate extends Evaluate[Order, BigDecimal] {
def evaluate(order: Order): BigDecimal = order match {
case GeneralOrder(products) => products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + ProductEvaluate.evaluate(p)
}
case ComplexOrder(orders) => orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + evaluate(o)
}
case CancelledOrder() => BigDecimal("0.0")
}}}
trait Evaluate[-A, T] { def evaluate(a: A): T }
object Order {
implicit object ProductEvaluate extends Evaluate[Product, BigDecimal] {
def evaluate(product: Product): BigDecimal = product match {
case BasicProduct(id, price) => price
case DiscountedProduct(discounted, discount) => evaluate(discounted) * (1 - discount)
case OutOfStock() => BigDecimal("0.0")
}}
implicit object OrderEvaluate extends Evaluate[Order, BigDecimal] {
def evaluate(order: Order): BigDecimal = order match {
case GeneralOrder(products) => products.foldLeft(BigDecimal("0.0")) {
case (acc, p) => acc + ProductEvaluate.evaluate(p)
}
case ComplexOrder(orders) => orders.foldLeft(BigDecimal("0.0")) {
case (acc, o) => acc + evaluate(o)
}
case CancelledOrder() => BigDecimal("0.0")
}}}
Act.5: “Enlightenment”
Research
Definitions
Definitions● Type classes
Definitions● Type classes
○ Ad-hoc polymorphism
Definitions● Type classes● Abstract Data Type (ADT)
Definitions● Type classes● Abstract Data Type (ADT)● Addable → Semigroup
Definitions● Type classes● Abstract Data Type (ADT)● Addable → Semigroup● AddableWithZero → Monoid
Definitions● Type classes● Abstract Data Type (ADT)● Addable → Semigroup● AddableWithZero → Monoid● Type classes come with laws!
Where to go next1. Functor2. Applicative3. Monad!
All are just type classes with some laws. That’s it!
Before we go, a bit of history...
Links & Resources● https://github.com/rabbitonweb/scala_typeclasses ● https://inoio.de/blog/2014/07/19/type-class-101-semigroup/ ● http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-
part-12-type-classes.html ● https://www.youtube.com/watch?v=sVMES4RZF-8 ● http://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf● https://www.destroyallsoftware.com/misc/reject.pdf● http://southpark.cc.com/avatar ● http://www.tandemic.com/wp-content/uploads/Definition.png
And that’s all folks!
And that’s all folks!
Paweł Szulc
And that’s all folks!
Paweł Szulc twitter: @rabbitonweb
And that’s all folks!
Paweł Szulc twitter: @rabbitonweb
blog: http://rabbitonweb.com
And that’s all folks!
Paweł Szulc twitter: @rabbitonweb
blog: http://rabbitonweb.com
github: https://github.com/rabbitonweb
And that’s all folks!
Paweł Szulc twitter: @rabbitonweb
blog: http://rabbitonweb.com
github: https://github.com/rabbitonweb
Questions?
Thank you!