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

JDD2015: Functional programing and Event Sourcing - a pair made in heaven - extended, 2 hours long brainwash - Paweł Szulc

358 views

Published on

Contact
FUNCTIONAL PROGRAMING AND EVENT SOURCING - A PAIR MADE IN HEAVEN - EXTENDED, 2 HOURS LONG BRAINWASH

TL;DR: This is talk is a solid introduction to two (supposedly) different topics: FP & ES. I will cover both the theory and the practice. We will emerage ES+FP application starting from ES+OO one.


While reading blogs or attending conferences, you might have heard about Event Sourcing. But didn't you get this feeling, that while there is a lot of theory out there, it is really hard to see a hands-on example? And even if you find some, those are always orbiting around Object Oriented concepts?
Greg Young once said "When we talk about Event Sourcing, current state is a left-fold of previous behaviours. Nothing new to Functional Programmers". If Functional Programming is such a natural concept for event sourced systems, shouldn't they fit together on a single codebase?
In this talk we will quickly introduce Event Sourcing (but without going into details), we will introduce some functional concepts as well (like State monad). Armoured with that knowledge we will try to transform sample ES application (OO-style, tightly coupled with framework) to frameworkless, FP-style solution).
Talk is targeted for beginner and intermediate audience. Examples will be in Scala but nothing fancy - normal syntax.


This talk is an extended version of a presentation "Event Sourcing & Functional Programming - a pair made in heaven". It is enriched with content of presentations: "Monads - asking the right question" and "It's all been done before - The Hitchhiker's Guide to Time Travel".

Published in: Software
  • Be the first to comment

  • Be the first to like this

JDD2015: Functional programing and Event Sourcing - a pair made in heaven - extended, 2 hours long brainwash - Paweł Szulc

  1. 1. Functional Programming & Event Sourcing A pair made in heaven twitter: @rabbitonweb, email: paul.szulc@gmail.com
  2. 2. Functional Programming & Event Sourcing A pair made in heaven twitter: @rabbitonweb, email: paul.szulc@gmail.com2h brainwash
  3. 3. Functional Programming
  4. 4. ● no assignment statements
  5. 5. ● no assignment statements ● no variables
  6. 6. ● no assignment statements ● no variables ● once given a value, never change
  7. 7. ● no assignment statements ● no variables ● once given a value, never change ● no side-effects at all
  8. 8. ● no assignment statements ● no variables ● once given a value, never change ● no side-effects at all
  9. 9. “The functional programmer sounds rather like a mediæval monk,
  10. 10. “The functional programmer sounds rather like a mediæval monk, denying himself the pleasures of life
  11. 11. “The functional programmer sounds rather like a mediæval monk, denying himself the pleasures of life in the hope that it will make him virtuous.”
  12. 12. case class User(id: Long, fn: String, ln: String)
  13. 13. case class User(id: Long, fn: String, ln: String) class Cache { def check(id: Long): Option[User] = ??? }
  14. 14. case class User(id: Long, fn: String, ln: String) class Cache { def check(id: Long): Option[User] = ??? } case class UserRepo(cache: Cache) { def retrieve(id: Long): User = ??? }
  15. 15. case class User(id: Long, fn: String, ln: String) class Cache { def check(id: Long): Option[User] = ??? } case class UserRepo(cache: Cache) { def retrieve(id: Long): User = ??? } class UserFinder(cache: Cache, repo: UserRepo) { }
  16. 16. case class User(id: Long, fn: String, ln: String) class Cache { def check(id: Long): Option[User] = ??? } case class UserRepo(cache: Cache) { def retrieve(id: Long): User = ??? } class UserFinder(cache: Cache, repo: UserRepo) { def findUser(id: Long): User = { } }
  17. 17. case class User(id: Long, fn: String, ln: String) class Cache { def check(id: Long): Option[User] = ??? } case class UserRepo(cache: Cache) { def retrieve(id: Long): User = ??? } class UserFinder(cache: Cache, repo: UserRepo) { def findUser(id: Long): User = { val maybeUser: Option[User] = cache.check(id) } }
  18. 18. case class User(id: Long, fn: String, ln: String) class Cache { def check(id: Long): Option[User] = ??? } case class UserRepo(cache: Cache) { def retrieve(id: Long): User = ??? } class UserFinder(cache: Cache, repo: UserRepo) { def findUser(id: Long): User = { val maybeUser: Option[User] = cache.check(id) if (maybeUser.isDefined) { maybeUser.get } } }
  19. 19. case class User(id: Long, fn: String, ln: String) class Cache { def check(id: Long): Option[User] = ??? } case class UserRepo(cache: Cache) { def retrieve(id: Long): User = ??? } class UserFinder(cache: Cache, repo: UserRepo) { def findUser(id: Long): User = { val maybeUser: Option[User] = cache.check(id) if (maybeUser.isDefined) { maybeUser.get } else { val user: User = repo.retrieve(id) cache.insert(user.id, user) } } }
  20. 20. No concept of ‘time’
  21. 21. f(input): output
  22. 22. input f(input): output
  23. 23. input f(input): output output
  24. 24. input f(input): output output g(input): output
  25. 25. input f(input): output output g(input): output output
  26. 26. input f(input): output output g(input): output output
  27. 27. h = g o f input f(input): output output g(input): output output
  28. 28. Modularity & composition
  29. 29. h = g o f input f(input): output output g(input): output output
  30. 30. /** * This function returns a reversed list * @param list A list to be reversed * @return A reversed list */ public List<T> reverse(List<t> list) { ??? }
  31. 31. /** * This function returns a reversed list * @param list A list to be reversed * @return A reversed list */ public List<T> reverse(List<t> list) { ??? }
  32. 32. /** * This function returns a reversed list * @param list A list to be reversed public List<T> reverse(List<t> list) { ??? }
  33. 33. /** * This function returns a reversed list public List<T> reverse(List<t> list) { ??? }
  34. 34. /** public List<T> reverse(List<t> list) { ??? }
  35. 35. public List<T> reverse(List<t> list) { ??? }
  36. 36. public List<T> reverse(List<t> list) { return list.sort(); }
  37. 37. def smdfknmsdfp[A](a: A): A = ???
  38. 38. def smdfknmsdfp[A](a: A): A = a
  39. 39. def identity[A](a: A): A = a
  40. 40. def smdfknmsdfp[A](a: A): A = a
  41. 41. def smdfknmsdfp(a: Int): Int = ???
  42. 42. def smdfknmsdfp(a: Int): Int = a = a + 10 = 10
  43. 43. def sum(list: List[Int]): Int =
  44. 44. def sum(list: List[Int]): Int = list match { case Nil => 0
  45. 45. def sum(list: List[Int]): Int = list match { case Nil => 0 case num :: tail => num + sum(tail) }
  46. 46. def sum(list: List[Int]): Int = list match { case Nil => 0 case num :: tail => num + sum(tail) }
  47. 47. def reduce[T, K] (list: List[T]): K =
  48. 48. def reduce[T, K] (list: List[T]): K = list match { case Nil => zero
  49. 49. def reduce[T, K] (zero: K) (list: List[T]): K = list match { case Nil => zero
  50. 50. def reduce[T, K] (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) }
  51. 51. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) }
  52. 52. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) }
  53. 53. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } def sum: List[Int] => Int =
  54. 54. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } def sum: List[Int] => Int = reduce(plus)(0)
  55. 55. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) }
  56. 56. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } def product: List[Int] => Int =
  57. 57. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } def product: List[Int] => Int = reduce(multiply)(1)
  58. 58. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) }
  59. 59. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } def anyTrue: List[Boolean] => Boolean =
  60. 60. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } def anyTrue: List[Boolean] => Boolean = reduce(or)(false)
  61. 61. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) }
  62. 62. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } def allTrue: List[Boolean] => Boolean =
  63. 63. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } def allTrue: List[Boolean] => Boolean = reduce(and)(true)
  64. 64. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) }
  65. 65. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } A :: B :: C :: D :: E :: Nil
  66. 66. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } A :: B :: C :: D :: E :: Nil A :: B :: C :: D :: E :: Nil
  67. 67. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } A :: B :: C :: D :: E :: Nil A :: B :: C :: D :: E :: zero
  68. 68. def reduce[T, K](op: T => K => K) (zero: K) (list: List[T]): K = list match { case Nil => zero case head :: tail => op(head)(reduce(op)(zero)(tail)) } A :: B :: C :: D :: E :: Nil A op B op C op D op E op zero
  69. 69. def <++>[T](a: List[T])(b: List[T]): List[T] =
  70. 70. def <++>[T](a: List[T])(b: List[T]): List[T] = reduce(::[T])(b)(a)
  71. 71. def lgth[T]: List[T] => Int = reduce(<+>)(0)
  72. 72. def <+>[T](el: T)(len: Int) = 1 + len def lgth[T]: List[T] => Int = reduce(<+>)(0)
  73. 73. def double(x: Int) = 2 * x
  74. 74. def double(x: Int) = 2 * x def doubleAll: List[Int] => List[Int] =
  75. 75. def double(x: Int) = 2 * x def doubleAll: List[Int] => List[Int] = reduce(<+>)(List.empty[Int])
  76. 76. def double(x: Int) = 2 * x def <+>(el: Int)(list: List[Int]) = double(el) :: list def doubleAll: List[Int] => List[Int] = reduce(<+>)(List.empty[Int])
  77. 77. def double(x: Int) = 2 * x def <+>(el: Int)(list: List[Int]) = double(el) :: list def doubleAll: List[Int] => List[Int] = reduce(<+>)(List.empty[Int])
  78. 78. def map[T](f: T => T) =
  79. 79. def map[T](f: T => T) = { reduce(<+>)(List.empty[T])(_) }
  80. 80. def map[T](f: T => T) = { def <+>(el: T)(list: List[T]) = f(el) :: list reduce(<+>)(List.empty[T])(_) }
  81. 81. def map[T](f: T => T) = { def <+>(el: T)(list: List[T]) = f(el) :: list reduce(<+>)(List.empty[T])(_) } def doubleAll: List[Int] => List[Int] = map(double)
  82. 82. def map[T](f: T => T) = { def <+>(el: T)(list: List[T]) = f(el) :: list reduce(<+>)(List.empty[T])(_) } def double(x: Int) = 2 * x def doubleAll: List[Int] => List[Int] = map(double)
  83. 83. case class TreeOf[T](label: T, subtrees: List[TreeOf[T]])
  84. 84. case class TreeOf[T](label: T, subtrees: List[TreeOf[T]]) def redtree[T, K,M] = tree match { case TreeOf(label, subtrees) => }
  85. 85. case class TreeOf[T](label: T, subtrees: List[TreeOf[T]]) def redtree[T, K,M](op1: T => K => M) = tree match { case TreeOf(label, subtrees) => op1(label)() }
  86. 86. case class TreeOf[T](label: T, subtrees: List[TreeOf[T]]) def redtree[T, K,M](op1: T => K => M) = tree match { case TreeOf(label, subtrees) => op1(label)(_redtree[T, K,M](op1)(op2)(zero)(subtrees)) }
  87. 87. def redtree[T, K,M](op1: T => K => M)(op2: M => K => K)(zero: K)(tree: TreeOf[T]): M = tree match { case TreeOf(label, subtrees) => op1(label)(_redtree[T, K,M](op1)(op2)(zero)(subtrees)) }
  88. 88. def _redtree[T, K, M](op1: T => K => M)(op2: M => K => K)(zero: K) (subtrees: List[TreeOf[T]]): K = def redtree[T, K,M](op1: T => K => M)(op2: M => K => K)(zero: K)(tree: TreeOf[T]): M = tree match { case TreeOf(label, subtrees) => op1(label)(_redtree[T, K,M](op1)(op2)(zero)(subtrees)) }
  89. 89. def _redtree[T, K, M](op1: T => K => M)(op2: M => K => K)(zero: K) (subtrees: List[TreeOf[T]]): K = subtrees match { case Nil => zero def redtree[T, K,M](op1: T => K => M)(op2: M => K => K)(zero: K)(tree: TreeOf[T]): M = tree match { case TreeOf(label, subtrees) => op1(label)(_redtree[T, K,M](op1)(op2)(zero)(subtrees)) }
  90. 90. def _redtree[T, K, M](op1: T => K => M)(op2: M => K => K)(zero: K) (subtrees: List[TreeOf[T]]): K = subtrees match { case Nil => zero case subtree :: rest => } def redtree[T, K,M](op1: T => K => M)(op2: M => K => K)(zero: K)(tree: TreeOf[T]): M = tree match { case TreeOf(label, subtrees) => op1(label)(_redtree[T, K,M](op1)(op2)(zero)(subtrees)) }
  91. 91. def _redtree[T, K, M](op1: T => K => M)(op2: M => K => K)(zero: K) (subtrees: List[TreeOf[T]]): K = subtrees match { case Nil => zero case subtree :: rest => redtree(op1)(op2)(zero)(subtree)) } def redtree[T, K,M](op1: T => K => M)(op2: M => K => K)(zero: K)(tree: TreeOf[T]): M = tree match { case TreeOf(label, subtrees) => op1(label)(_redtree[T, K,M](op1)(op2)(zero)(subtrees)) }
  92. 92. def _redtree[T, K, M](op1: T => K => M)(op2: M => K => K)(zero: K) (subtrees: List[TreeOf[T]]): K = subtrees match { case Nil => zero case subtree :: rest => redtree(op1)(op2)(zero)(subtree)) (_redtree(op1)(op2)(zero)(rest)) } def redtree[T, K,M](op1: T => K => M)(op2: M => K => K)(zero: K)(tree: TreeOf[T]): M = tree match { case TreeOf(label, subtrees) => op1(label)(_redtree[T, K,M](op1)(op2)(zero)(subtrees)) }
  93. 93. def _redtree[T, K, M](op1: T => K => M)(op2: M => K => K)(zero: K) (subtrees: List[TreeOf[T]]): K = subtrees match { case Nil => zero case subtree :: rest => op2(redtree(op1)(op2)(zero)(subtree)) (_redtree(op1)(op2)(zero)(rest)) } def redtree[T, K,M](op1: T => K => M)(op2: M => K => K)(zero: K)(tree: TreeOf[T]): M = tree match { case TreeOf(label, subtrees) => op1(label)(_redtree[T, K,M](op1)(op2)(zero)(subtrees)) }
  94. 94. def sumtree = redtree(plus)(plus)(0)(_)
  95. 95. def labels[T]: TreeOf[T] => List[T] = redtree(::[T])(<++>[T])(List.empty[T])
  96. 96. def mapTree[T, K](f: T => K): TreeOf[T] => TreeOf[K] = { def <+>(label: T)(subtrees: List[TreeOf[K]]) = TreeOf(f(label), subtrees) redtree(<+>)(::[TreeOf[K]])(List.empty[TreeOf[K]]) }
  97. 97. “Why Functional Programming Matters” J. Hughes http://comjnl.oxfordjournals.org/content/32/2/98. full.pdf
  98. 98. “Why Functional Programming Matters” J. Hughes, Nov. 1988 http://comjnl.oxfordjournals.org/content/32/2/98. full.pdf
  99. 99. Soft introduction “Why Functional Programming Matters” J. Hughes, Nov. 1988 http://comjnl.oxfordjournals.org/content/32/2/98. full.pdf
  100. 100. “Program Design by Calculation” J.N. Oliveira http://www4.di.uminho.pt/~jno/ps/pdbc_part.pdf
  101. 101. “Program Design by Calculation” J.N. Oliveira, Draft http://www4.di.uminho.pt/~jno/ps/pdbc_part.pdf
  102. 102. Patterns in FP World “Program Design by Calculation” J.N. Oliveira, Draft http://www4.di.uminho.pt/~jno/ps/pdbc_part.pdf
  103. 103. Patterns in FP World Math Matters “Program Design by Calculation” J.N. Oliveira, Draft http://www4.di.uminho.pt/~jno/ps/pdbc_part.pdf
  104. 104. “A lengthy approach to Haskell fundamentals” http://www.davesquared.net/2012/05/lengthy- approach-to-haskell.html
  105. 105. Functional Programming
  106. 106. Functional Programming
  107. 107. https://xkcd.com/1312/
  108. 108. How to do something useful?
  109. 109. How to do something useful?
  110. 110. Monads Asking the right question
  111. 111. WTF Monad?
  112. 112. WTF Monad?
  113. 113. “Hi”
  114. 114. “Sup?”
  115. 115. “I'm ready to write my first program. How can I print some stuff to console?”
  116. 116. “Use IO Monad”
  117. 117. “What the *** is a monad?”
  118. 118. WTF Monad?
  119. 119. 1. We understand code
  120. 120. 1. We understand code 2. It is easier to talk about related ideas and then abstract to something more general
  121. 121. 1. We understand code 2. It is easier to talk about related ideas and then abstract to something more general 3. Having abstract model created, we tend to create metaphors
  122. 122. 1. We understand code 2. It is easier to talk about related ideas and then abstract to something more general 3. Having abstract model created, we tend to create metaphors 4. Having all above we can formalize
  123. 123. Code & related ideas
  124. 124. Android, iOS, Windows Phone Partner Lookup
  125. 125. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
  126. 126. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String)
  127. 127. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String =
  128. 128. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
  129. 129. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10")
  130. 130. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10") var penny = Geek("Penny", workPlace = cheeseCakeFactory) var leonard = Geek("Leonard", workPlace = university)
  131. 131. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10") var penny = Geek("Penny", workPlace = cheeseCakeFactory) var leonard = Geek("Leonard", workPlace = university) penny = penny.copy(partner = leonard) leonard = leonard.copy(partner = penny)
  132. 132. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10") var penny = Geek("Penny", workPlace = cheeseCakeFactory) var leonard = Geek("Leonard", workPlace = university) penny = penny.copy(partner = leonard) leonard = leonard.copy(partner = penny) println(partnerLookup(leonard)) println(partnerLookup(penny))
  133. 133. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10") var penny = Geek("Penny", workPlace = cheeseCakeFactory) var leonard = Geek("Leonard", workPlace = university) penny = penny.copy(partner = leonard) leonard = leonard.copy(partner = penny) println(partnerLookup(leonard)) // output: "Cake Street 1" println(partnerLookup(penny))
  134. 134. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10") var penny = Geek("Penny", workPlace = cheeseCakeFactory) var leonard = Geek("Leonard", workPlace = university) penny = penny.copy(partner = leonard) leonard = leonard.copy(partner = penny) println(partnerLookup(leonard)) // output: "Cake Street 1" println(partnerLookup(penny)) // output: "Academic St. 10"
  135. 135. “This app sucks!”
  136. 136. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
  137. 137. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
  138. 138. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val university = WorkPlace("University", "Academic St. 10")
  139. 139. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val university = WorkPlace("University", "Academic St. 10") val rajesh = Geek("Rajesh", workPlace = university)
  140. 140. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val university = WorkPlace("University", "Academic St. 10") val rajesh = Geek("Rajesh", workPlace = university) println(partnerLookup(rajesh))
  141. 141. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val university = WorkPlace("University", "Academic St. 10") val rajesh = Geek("Rajesh", workPlace = university) println(partnerLookup(rajesh)) // java.lang.NullPointerException // at wtf.examples.A1$.partnerLookUp(A1.scala:14) // at ...
  142. 142. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace) case class WorkPlace(name: String, street: String) def partnerLookup(geek: Geek): String = geek.partner.workPlace.street val university = WorkPlace("University", "Academic St. 10") val rajesh = Geek("Rajesh", workPlace = university) println(partnerLookup(rajesh)) // java.lang.NullPointerException // at wtf.examples.A1$.partnerLookUp(A1.scala:14) // at ...
  143. 143. def partnerLookup(geek: Geek): String = { }
  144. 144. def partnerLookup(geek: Geek): String = { if(geek != null) { }
  145. 145. def partnerLookup(geek: Geek): String = { if(geek != null) { if(geek.partner != null) { }
  146. 146. def partnerLookup(geek: Geek): String = { if(geek != null) { if(geek.partner != null) { if(geek.partner.workPlace != null) { }
  147. 147. def partnerLookup(geek: Geek): String = { if(geek != null) { if(geek.partner != null) { if(geek.partner.workPlace != null) { if(geek.partner.workPlace.street != null) { }
  148. 148. def partnerLookup(geek: Geek): String = { if(geek != null) { if(geek.partner != null) { if(geek.partner.workPlace != null) { if(geek.partner.workPlace.street != null) { return geek.partner.workPlace.street } } } } }
  149. 149. def partnerLookup(geek: Geek): String = { if(geek != null) { if(geek.partner != null) { if(geek.partner.workPlace != null) { if(geek.partner.workPlace.street != null) { return geek.partner.workPlace.street } } } } "not found" }
  150. 150. val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10") var penny = Geek("Penny", workPlace = cheeseCakeFactory) var leonard = Geek("Leonard", workPlace = university) val rajesh = Geek("Rajesh", workPlace = university) penny = penny.copy(partner = leonard) leonard = leonard.copy(partner = penny) println(partnerLookup(leonard)) // output: "Cake Street 1" println(partnerLookup(penny)) // output: "Academic St. 10" println(partnerLookup(rajesh)) // output: "not found"
  151. 151. def partnerLookup(geek: Geek): String = { if(geek != null) { if(geek.partner != null) { if(geek.partner.workPlace != null) { if(geek.partner.workPlace.street != null) { return geek.partner.workPlace.street } } } } "not found" }
  152. 152. Call me: Maybe
  153. 153. sealed trait Maybe[+A]
  154. 154. sealed trait Maybe[+A] case class Some[+A](value: A) extends Maybe[A]
  155. 155. sealed trait Maybe[+A] case class Some[+A](value: A) extends Maybe[A] case object None extends Maybe[Nothing]
  156. 156. sealed trait Maybe[+A] case class Some[+A](value: A) extends Maybe[A] case object None extends Maybe[Nothing] object Maybe { def apply[A](value: A) = Some(value) }
  157. 157. sealed trait Maybe[+A] { } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { } case object None extends Maybe[Nothing] { }
  158. 158. sealed trait Maybe[+A] { def andThen[B](f: A => Maybe[B]): Maybe[B] } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { } case object None extends Maybe[Nothing] { }
  159. 159. sealed trait Maybe[+A] { def andThen[B](f: A => Maybe[B]): Maybe[B] } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value) } case object None extends Maybe[Nothing] { }
  160. 160. sealed trait Maybe[+A] { def andThen[B](f: A => Maybe[B]): Maybe[B] } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value) } case object None extends Maybe[Nothing] { def andThen[B](f: Nothing => Maybe[B]): Maybe[B] = this }
  161. 161. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace]) case class WorkPlace(name: String, street: Maybe[String]) def partnerLookup(geek: Geek): String =
  162. 162. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace]) case class WorkPlace(name: String, street: Maybe[String]) def partnerLookup(geek: Geek): String = geek.partner
  163. 163. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace]) case class WorkPlace(name: String, street: Maybe[String]) def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace)
  164. 164. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace]) case class WorkPlace(name: String, street: Maybe[String]) def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street)
  165. 165. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace]) case class WorkPlace(name: String, street: Maybe[String]) def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street }
  166. 166. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace]) case class WorkPlace(name: String, street: Maybe[String]) def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" }
  167. 167. val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10") var penny = Geek("Penny", workPlace = cheeseCakeFactory) var leonard = Geek("Leonard", workPlace = university) val rajesh = Geek("Rajesh", workPlace = university) penny = penny.copy(partner = leonard) leonard = leonard.copy(partner = penny) println(partnerLookup(leonard)) println(partnerLookup(penny)) println(partnerLookup(rajesh))
  168. 168. val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1") val university = WorkPlace("University", "Academic St. 10") var penny = Geek("Penny", workPlace = cheeseCakeFactory) var leonard = Geek("Leonard", workPlace = university) val rajesh = Geek("Rajesh", workPlace = university) penny = penny.copy(partner = leonard) leonard = leonard.copy(partner = penny) println(partnerLookup(leonard)) // output: "Cake Street 1" println(partnerLookup(penny)) // output: "Academic St. 10" println(partnerLookup(rajesh)) // output: "not found"
  169. 169. def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" }
  170. 170. def partnerLookup(geek: Geek): String = geek.partner.workPlace.street def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" }
  171. 171. def partnerLookup(geek: Geek): String = geek.partner.workPlace.street def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" }
  172. 172. def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" }
  173. 173. def partnerLookup(geek: Geek): String = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): String =
  174. 174. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] =
  175. 175. with a little bit of magic For-comprehension
  176. 176. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { }
  177. 177. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ }
  178. 178. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(g => g.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  179. 179. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  180. 180. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  181. 181. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  182. 182. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  183. 183. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  184. 184. def partnerLookup(geek: Geek): Maybe[String] = geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match { case wtf.monads.Some(street) => street case wtf.monads.None => "not found" } def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  185. 185. var leonard = Geek("Leonard", workPlace = university, partner = penny) partnerLookup(leonard) // Some("Cake Street 1") // how to print it?
  186. 186. sealed trait Maybe[+A] { def andThen[B](f: A => Maybe[B]): Maybe[B] } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value) } case object None extends Maybe[Nothing] { def andThen[B](f: Nothing => Maybe[B]): Maybe[B] = this }
  187. 187. sealed trait Maybe[+A] { def andThen[B](f: A => Maybe[B]): Maybe[B] def within[B](f: A => B): Maybe[B] } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value) } case object None extends Maybe[Nothing] { def andThen[B](f: Nothing => Maybe[B]): Maybe[B] = this }
  188. 188. sealed trait Maybe[+A] { def andThen[B](f: A => Maybe[B]): Maybe[B] def within[B](f: A => B): Maybe[B] } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value) } case object None extends Maybe[Nothing] { def andThen[B](f: Nothing => Maybe[B]): Maybe[B] = this } Reuse existing functionality like println
  189. 189. sealed trait Maybe[+A] { def andThen[B](f: A => Maybe[B]): Maybe[B] def within[B](f: A => B): Maybe[B] } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value) def within[B](f: A => B): Maybe[B] = Maybe(f(value)) } case object None extends Maybe[Nothing] { def andThen[B](f: Nothing => Maybe[B]): Maybe[B] = this }
  190. 190. sealed trait Maybe[+A] { def andThen[B](f: A => Maybe[B]): Maybe[B] def within[B](f: A => B): Maybe[B] } object Maybe { def apply[A](value: A) = Some(value) } case class Some[+A](value: A) extends Maybe[A] { def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value) def within[B](f: A => B): Maybe[B] = Maybe(f(value)) } case object None extends Maybe[Nothing] { def andThen[B](f: Nothing => Maybe[B]): Maybe[B] = this def within[B](f: Nothing => B): Maybe[B] = this }
  191. 191. var leonard = Geek("Leonard", workPlace = university, partner = penny) partnerLookup(leonard) // Some("Cake Street 1") // how to print it?
  192. 192. var leonard = Geek("Leonard", workPlace = university, partner = penny) partnerLookup(leonard)
  193. 193. var leonard = Geek("Leonard", workPlace = university, partner = penny) partnerLookup(leonard).within(println) // output: "Cake Street 1"
  194. 194. Android, iOS, Windows Phone Ship Inventory
  195. 195. case class Pirate(name: String, ships: Many[Ship]) case class Ship(name: String, hold: Hold) case class Hold(barrel: Many[Barrel]) case class Barrel(amount: Int)
  196. 196. case class Pirate(name: String, ships: Many[Ship]) case class Ship(name: String, hold: Hold) case class Hold(barrel: Many[Barrel]) case class Barrel(amount: Int) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
  197. 197. case class Pirate(name: String, ships: Many[Ship]) case class Ship(name: String, hold: Hold) case class Hold(barrel: Many[Barrel]) case class Barrel(amount: Int) val blackPearl = Ship("Black Pearl", blackHold) val whitePearl = Ship("White Pearl", whiteHold) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
  198. 198. case class Pirate(name: String, ships: Many[Ship]) case class Ship(name: String, hold: Hold) case class Hold(barrel: Many[Barrel]) case class Barrel(amount: Int) val blackHold = Hold(Empty) val whiteHold = Hold(Many(Barrel(20), Barrel(10))) val blackPearl = Ship("Black Pearl", blackHold) val whitePearl = Ship("White Pearl", whiteHold) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
  199. 199. sealed trait Many[+A]
  200. 200. sealed trait Many[+A] case class Const[+A](head: A, tail: Many[A]) extends Many[A]
  201. 201. sealed trait Many[+A] case class Const[+A](head: A, tail: Many[A]) extends Many[A] case object Empty extends Many[Nothing]
  202. 202. sealed trait Many[+A] case class Const[+A](head: A, tail: Many[A]) extends Many[A] case object Empty extends Many[Nothing] object Many { def apply[A](elements: A*): Many[A] = { if(elements.length == 1) Const(elements(0), Empty) else Const(elements(0), apply(elements.drop(1) : _*)) } }
  203. 203. sealed trait Many[+A] { def andThen[B](f: A => Many[B]): Many[B] def within[B](f: A => B): Many[B] }
  204. 204. case class Const[+A](head: A, tail: Many[A]) extends Many[A] { def andThen[B](f: A => Many[B]): Many[B] = ... def within[B](f: A => B): Many[B] = ... }
  205. 205. case class Const[+A](head: A, tail: Many[A]) extends Many[A] { def andThen[B](f: A => Many[B]): Many[B] = ... def within[B](f: A => B): Many[B] = Const(f(head), tail.within(f)) }
  206. 206. case class Const[+A](head: A, tail: Many[A]) extends Many[A] { def andThen[B](f: A => Many[B]): Many[B] = concat( f(head), tail.andThen(f) ) def within[B](f: A => B): Many[B] = Const(f(head), tail.within(f)) }
  207. 207. case class Const[+A](head: A, tail: Many[A]) extends Many[A] { def andThen[B](f: A => Many[B]): Many[B] = concat( f(head), tail.andThen(f) ) private def concat[A](first: Many[A], second: Many[A]): Many[A] = { … } def within[B](f: A => B): Many[B] = Const(f(head), tail.within(f)) }
  208. 208. case class Const[+A](head: A, tail: Many[A]) extends Many[A] { def andThen[B](f: A => Many[B]): Many[B] = concat( f(head), tail.andThen(f) ) private def concat[A](first: Many[A], second: Many[A]): Many[A] = { … } def within[B](f: A => B): Many[B] = Const(f(head), tail.within(f)) } case object Empty extends Many[Nothing] { def andThen[B](f: Nothing => Many[B]): Many[B] = this def within[B](f: Nothing => B): Many[B] = this }
  209. 209. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
  210. 210. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl)) jack.ships.andThen(ship => ship.hold.barrel) .within(barrel => barrel.amount)
  211. 211. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl)) jack.ships.andThen(ship => ship.hold.barrel) .within(barrel => barrel.amount) import Magic._ val amounts = for { ship <- jack.ships barrel <- ship.hold.barrel } yield (barrel.amount)
  212. 212. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl)) jack.ships.andThen(ship => ship.hold.barrel) .within(barrel => barrel.amount) import Magic._ val amounts = for { ship <- jack.ships barrel <- ship.hold.barrel } yield (barrel.amount)
  213. 213. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl)) jack.ships.andThen(ship => ship.hold.barrel) .within(barrel => barrel.amount) import Magic._ val amounts = for { ship <- jack.ships barrel <- ship.hold.barrel } yield (barrel.amount)
  214. 214. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl)) jack.ships.andThen(ship => ship.hold.barrel) .within(barrel => barrel.amount) import Magic._ val amounts = for { ship <- jack.ships barrel <- ship.hold.barrel } yield (barrel.amount)
  215. 215. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl)) jack.ships.andThen(ship => ship.hold.barrel) .within(barrel => barrel.amount) import Magic._ val amounts = for { ship <- jack.ships barrel <- ship.hold.barrel } yield (barrel.amount)
  216. 216. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl)) jack.ships.andThen(ship => ship.hold.barrel) .within(barrel => barrel.amount) import Magic._ val amounts = for { ship <- jack.ships barrel <- ship.hold.barrel } yield (barrel.amount)
  217. 217. val blackPearl = Ship("Black Pearl", Hold(Empty)) val whitePearl = Ship("White Pearl", Hold(Many(Barrel(20), Barrel(10)))) val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl)) jack.ships.andThen(ship => ship.hold.barrel) .within(barrel => barrel.amount) import Magic._ val amounts = for { ship <- jack.ships barrel <- ship.hold.barrel } yield (barrel.amount) println(amounts) // Const(20,Const(10,Empty))
  218. 218. ● We've seen two wrapping classes.
  219. 219. ● We've seen two wrapping classes. ● All have andThen method.
  220. 220. ● We've seen two wrapping classes. ● All have andThen method. ● Each can take a function that can be called on a value wrapped by the structure (which can be empty or there can be multiple instances of it).
  221. 221. Both were examples of a Monad!
  222. 222. WTF Monad?
  223. 223. Monad is a container, a box.
  224. 224. Monad is a container, a box. A box in which we can store an object. Wrap it.
  225. 225. Monad is a container, a box. A box in which we can store an object, a value. A box that has a common interface, which allows us to do one thing: connect sequence of operations on the content of the box.
  226. 226. Monad is a container, a box. A box in which we can store an object, a value. A box that has a common interface, which allows us to do one thing: connect sequence of operations on the content of the box. andThen means "do the next things".
  227. 227. Monad is a container, a box. A box in which we can store an object, a value. A box that has a common interface, which allows us to do one thing: connect sequence of operations on the content of the box. andThen means "do the next things". But the way it is done depends on a box.
  228. 228. Monad is a container, a box. A box in which we can store an object, a value. A box that has a common interface, which allows us to do one thing: connect sequence of operations on the content of the box. andThen means "do the next things". But the way it is done depends on a box.
  229. 229. Monad is a container, a box. A box in which we can store an object, a value. A box that has a common interface, which allows us to do one thing: connect sequence of operations on the content of the box. andThen means "do the next things". But the way it is done depends on a box.
  230. 230. Monad is a container, a box. A box in which we can store an object, a value. A box that has a common interface, which allows us to do one thing: connect sequence of operations on the content of the box. andThen means "do the next things". But the way it is done depends on a box.
  231. 231. Monad is an abstract data type.
  232. 232. Monad is an abstract data type. It has two operators: andThen and unit
  233. 233. Monad is an abstract data type. It has two operators: andThen and unit object Maybe { def apply[A](value: A) = Some(value) }
  234. 234. Monad is an abstract data type. It has two operators: andThen and unit
  235. 235. Monad is an abstract data type. It has two operators: andThen and unit
  236. 236. Monad is an abstract data type. It has two operators: andThen and unit bind (>>=) // formal name
  237. 237. Monad is an abstract data type. It has two operators: andThen and unit bind (>>=) // formal name flatMap // in Scala
  238. 238. def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  239. 239. def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  240. 240. object Magic { case class RichMaybe[A](m: Maybe[A]) { def flatMap[B](f: A => Maybe[B]): Maybe[B] = m.andThen(f) def map[B](f: A => B): Maybe[B] = m.within(f) } implicit def enrich[A](m: Maybe[A]) = RichMaybe(m) }
  241. 241. def partnerLookup(geek: Geek): Maybe[String] = { import Magic._ for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  242. 242. def partnerLookup(geek: Geek): Maybe[String] = { for { p <- geek.partner wp <- p.workPlace s <- wp.street } yield (s) }
  243. 243. Monad is an abstract data type. It has two operators: bind and unit
  244. 244. Monad is an abstract data type. It has two operators: bind and unit Those operators must follow certain rules: 1. associativity 2. left identity 3. right identity
  245. 245. 1. associativity 2. left identity 3. right identity unit acts approximately as a neutral element of bind
  246. 246. 1. associativity 2. left identity 3. right identity unit acts approximately as a neutral element of bind unit allows us to put value into the box without damaging it
  247. 247. 1. associativity 2. left identity 3. right identity unit acts approximately as a neutral element of bind unit(x).flatMap(f) == f(x) m.flatMap(unit) == m unit allows us to put value into the box without damaging it
  248. 248. 1. associativity 2. left identity 3. right identity “Binding two functions in succession is the same as binding one function that can be determined from them”
  249. 249. 1. associativity 2. left identity 3. right identity m.flatMap{f(_)}.flatMap.{g(_)} == m.flatMap{f(_).flatMap.{g(_)}} “Binding two functions in succession is the same as binding one function that can be determined from them”
  250. 250. WTF Monad?
  251. 251. case class User(id: Long, fn: String, ln: String) class Cache { def check(id: Long): Option[User] = ??? } case class UserRepo(cache: Cache) { def retrieve(id: Long): User = ??? } class UserFinder(cache: Cache, repo: UserRepo) { def findUser(id: Long): User = { val maybeUser: Option[User] = cache.check(id) if (maybeUser.isDefined) { maybeUser.get } else { val user: User = repo.retrieve(id) cache.insert(user.id, user) } } }
  252. 252. case class User(id: Long, fn: String, ln: String)
  253. 253. case class User(id: Long, fn: String, ln: String) class Cache {}
  254. 254. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ...
  255. 255. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ... def retrieve(id: Long)(cache: Cache): (Cache, User) = ...
  256. 256. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ... def retrieve(id: Long)(cache: Cache): (Cache, User) = ... def findUser(id: Long)(cache: Cache): (Cache, User) = { } }
  257. 257. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ... def retrieve(id: Long)(cache: Cache): (Cache, User) = ... def findUser(id: Long)(cache: Cache): (Cache, User) = { val (c, mu) = check(id)(cache) } }
  258. 258. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ... def retrieve(id: Long)(cache: Cache): (Cache, User) = ... def findUser(id: Long)(cache: Cache): (Cache, User) = { val (c, mu) = check(id)(cache) mu match { case Some(u) => (c, u) } } }
  259. 259. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ... def retrieve(id: Long)(cache: Cache): (Cache, User) = ... def findUser(id: Long)(cache: Cache): (Cache, User) = { val (c, mu) = check(id)(cache) mu match { case Some(u) => (c, u) case None => retrieve(id)(c) } } }
  260. 260. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ... def retrieve(id: Long)(cache: Cache): (Cache, User) = ... def findUser(id: Long)(cache: Cache): (Cache, User) = { val (c, mu) = check(id)(cache) mu match { case Some(u) => (c, u) case None => retrieve(id)(c) } } }
  261. 261. S => (S, A)
  262. 262. State[S, A] S => (S, A)
  263. 263. State[S, A] S => (S, A) .run(S)
  264. 264. State[S, A] S => (S, A) .map(A => B): State[S, B]
  265. 265. State[S, A] S => (S, A) .flatMap(A => State[S, B]): State[S,B]
  266. 266. object State { def apply[S, A] (f: S => (S,A)): State[S, A] = }
  267. 267. object State { def apply[S, A] (f: S => (S,A)): State[S, A] = new State[S, A] { def run(s: S) = f(s) } }
  268. 268. object State { def apply[S, A] (f: S => (S,A)): State[S, A] = new State[S, A] { def run(s: S) = f(s) } } def check(id: String) = State[Cache, Option[User]].apply { }
  269. 269. object State { def apply[S, A] (f: S => (S,A)): State[S, A] = new State[S, A] { def run(s: S) = f(s) } } def check(id: String) = State[Cache, Option[User]].apply { (c: Cache) => (c, c.get(id)) }
  270. 270. object State { def apply[S, A] (f: S => (S,A)): State[S, A] = new State[S, A] { def run(s: S) = f(s( } } def check(id: String) = State[Cache, Option[User]]{ (c: Cache) => (c, c.get(id)) }
  271. 271. trait State[S, +A] { }
  272. 272. trait State[S, +A] { def run(initial: S): (S, A) }
  273. 273. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = } }
  274. 274. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { } } }
  275. 275. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => (_, _ ) } } }
  276. 276. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => (_, f(a)) } } }
  277. 277. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => val (s, a) = run(s0) (_, f(a)) } } }
  278. 278. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => val (s, a) = run(s0) (s, f(a)) } } }
  279. 279. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => val (s, a) = run(s0) (s, f(a)) } } def flatMap[B](f: A => State[S,B]): State[S, B] = } }
  280. 280. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => val (s, a) = run(s0) (s, f(a)) } } def flatMap[B](f: A => State[S,B]): State[S, B] = f(a) } }
  281. 281. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => val (s, a) = run(s0) (s, f(a)) } } def flatMap[B](f: A => State[S,B]): State[S, B] = val (s, a) = run(s0) f(a) } }
  282. 282. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => val (s, a) = run(s0) (s, f(a)) } } def flatMap[B](f: A => State[S,B]): State[S, B] = State { s0 => val (s, a) = run(s0) f(a) } } }
  283. 283. trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s0 => val (s, a) = run(s0) (s, f(a)) } } def flatMap[B](f: A => State[S,B]): State[S, B] = State { s0 => val (s, a) = run(s0) f(a).run(s) } } }
  284. 284. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ??? def retrieve(id: Long)(cache: Cache): (Cache, User) = ??? def findUser(id: Long)(cache: Cache): (Cache, User) = { val (c, mu) = check(id)(cache) mu match { case Some(u) => (c, u) case None => retrieve(id)(c) } } }
  285. 285. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long)(cache: Cache): (Cache, Option[User]) = ??? def retrieve(id: Long)(cache: Cache): (Cache, User) = ??? def findUser(id: Long)(cache: Cache): (Cache, User) = { val (c, mu) = check(id)(cache) mu match { case Some(u) => (c, u) case None => retrieve(id)(c) } } }
  286. 286. case class User(id: Long, fn: String, ln: String) class Cache {} def check(id: Long): State[Cache, Option[User]] = ??? def retrieve(id: Long): State[Cache, User] = ??? def findUser(id: Long): State[Cache, User] = { for { maybeUser <- check(id) user <- maybeUser match { case Some(u) => State { c => (c, u)} case None => retrieve(id) } } yield (user) }
  287. 287. Event sourcing
  288. 288. Event Sourcing
  289. 289. Event Sourcing driven by business
  290. 290. Bloggers Conf App
  291. 291. Bloggers Conf App ● Can create an account
  292. 292. Bloggers Conf App ● Can create an account ● List all bloggers already using the app
  293. 293. Bloggers Conf App ● Can create an account ● List all bloggers already using the app ● Mark/unmark other blogger as a friend
  294. 294. Bloggers Conf App ● Can create an account ● List all bloggers already using the app ● Mark/unmark other blogger as a friend ● Mark/unmark other blogger as an enemy
  295. 295. Bloggers Conf App ● Can create an account ● List all bloggers already using the app ● Mark/unmark other blogger as a friend ● Mark/unmark other blogger as an enemy ● Deactivate its account
  296. 296. Bloggers Conf App Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T
  297. 297. Bloggers Conf App Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T Friends id friend_id 3 1
  298. 298. Bloggers Conf App Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T Friends id friend_id 3 1 Enemies id enemy_id 3 2
  299. 299. Structure is not that important
  300. 300. Structure is not that important Changes more often than behaviour
  301. 301. Start thinking about facts occurring
  302. 302. Start thinking about facts occurring Derive structure from them
  303. 303. Blogger Account Created (id=3)
  304. 304. Blogger Account Created (id=3) Befriended Blogger id=1
  305. 305. Blogger Account Created (id=3) Befriended Blogger id=1 Made Enemy of Blogger id=2
  306. 306. Blogger Account Created (id=3) Befriended Blogger id=1 Made Enemy of Blogger id=2 Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T Friends id friend_id Enemies id enemy_id
  307. 307. Blogger Account Created (id=3) Befriended Blogger id=1 Made Enemy of Blogger id=2 Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T Friends id friend_id Enemies id enemy_id
  308. 308. Blogger Account Created (id=3) Befriended Blogger id=1 Made Enemy of Blogger id=2 Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T Friends id friend_id 3 1 Enemies id enemy_id
  309. 309. Blogger Account Created (id=3) Befriended Blogger id=1 Made Enemy of Blogger id=2 Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T Friends id friend_id 3 1 Enemies id enemy_id 3 2
  310. 310. Blogger Account Created (id=3) Befriended Blogger id=1 Made Enemy of Blogger id=2
  311. 311. Blogger Account Created (id=3) Befriended Blogger id=1 Made Enemy of Blogger id=2 Befriended Blogger id=2 Unfriended Blogger id=2 Blogger Account Created (id=3) Befriended Blogger id=1 Made Enemy of Blogger id=2
  312. 312. Event Sourcing driven by business
  313. 313. Event Sourcing The only model that does not lose data
  314. 314. “Enemy of my enemy is my friend”
  315. 315. Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T Friends id friend_id 3 1 Enemies id enemy_id 3 2
  316. 316. Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T 4 Tomasz Młynarski T Friends id friend_id 3 1 Enemies id enemy_id 3 2 4 2
  317. 317. Bloggers id first_name last_name active 1 Jan Kowalski T 2 Krystian Nowak T 3 Malgorzata Kucharska T 4 Tomasz Młynarski T 5 Monika Jagoda T Friends id friend_id 3 1 Enemies id enemy_id 3 2 4 2 2 5
  318. 318. id = 3 id = 2 id = 1 id = 4 id = 5 id = 6
  319. 319. id = 3 id = 2 id = 1 id = 4 id = 5 id = 6
  320. 320. id = 3 id = 2 id = 1 id = 4 id = 5 id = 6
  321. 321. id = 3 id = 2 id = 1 id = 4 id = 5 id = 6
  322. 322. id = 3 id = 2 id = 1 id = 4 id = 5 id = 6
  323. 323. id = 3 id = 2 id = 1 id = 4 id = 5 id = 6
  324. 324. id = 3 id = 2 id = 1 id = 4 id = 5 id = 6
  325. 325. id = 3 id = 2 id = 1 id = 4 id = 5 id = 6
  326. 326. Benefits of Event Sourcing
  327. 327. Benefits of Event Sourcing ● Ability to go in time and figure out exactly what have happened
  328. 328. Benefits of Event Sourcing ● Ability to go in time and figure out exactly what have happened ● Scientific measurements over time, compare time periods
  329. 329. Benefits of Event Sourcing ● Ability to go in time and figure out exactly what have happened ● Scientific measurements over time, compare time periods ● Built-in audit log
  330. 330. Benefits of Event Sourcing ● Ability to go in time and figure out exactly what have happened ● Scientific measurements over time, compare time periods ● Built-in audit log ● Enables temporal querying
  331. 331. Benefits of Event Sourcing ● Ability to go in time and figure out exactly what have happened ● Scientific measurements over time, compare time periods ● Built-in audit log ● Enables temporal querying ● Fits well with machine learning
  332. 332. Benefits of Event Sourcing ● Ability to go in time and figure out exactly what have happened ● Scientific measurements over time, compare time periods ● Built-in audit log ● Enables temporal querying ● Fits well with machine learning ● Preserves history - question not yet asked
  333. 333. Benefits of Event Sourcing ● Ability to go in time and figure out exactly what have happened ● Scientific measurements over time, compare time periods ● Built-in audit log ● Enables temporal querying ● Fits well with machine learning ● Preserves history - question not yet asked ● Writing regression tests is easy
  334. 334. Benefits of Event Sourcing ● Ability to go in time and figure out exactly what have happened ● Scientific measurements over time, compare time periods ● Built-in audit log ● Enables temporal querying ● Fits well with machine learning ● Preserves history - question not yet asked ● Writing regression tests is easy ● Polyglot data
  335. 335. Drawbacks of Event Sourcing
  336. 336. Drawbacks of Event Sourcing ● Historical record of your bad decisions
  337. 337. Drawbacks of Event Sourcing ● Historical record of your bad decisions ● Handling event duplicates
  338. 338. Drawbacks of Event Sourcing ● Historical record of your bad decisions ● Handling event duplicates ● Data eventually consistent
  339. 339. How to implement it?
  340. 340. Events vs Commands
  341. 341. Journal
  342. 342. Journal
  343. 343. Journal val id = “” val firstName: String = “” val lastName: String = “” val friends: List[String] = List()
  344. 344. Journal val id = “” val firstName: String = “” val lastName: String = “” val friends: List[String] = List()
  345. 345. Journal val id = “” val firstName: String = “” val lastName: String = “” val friends: List[String] = List()
  346. 346. Journal val id = “” val firstName: String = “” val lastName: String = “” val friends: List[String] = List() Initialized(“1”, “Jan”, “Kowalski”
  347. 347. Journal val id = “” val firstName: String = “” val lastName: String = “” val friends: List[String] = List() Initialized(“1”, “Jan”, “Kowalski”
  348. 348. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List()
  349. 349. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List()
  350. 350. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List()
  351. 351. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List() Befriended(“10”)
  352. 352. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List() Befriended(“10”)
  353. 353. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List(“10”)
  354. 354. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”)
  355. 355. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”)
  356. 356. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”)
  357. 357. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List(“10”, “31”)
  358. 358. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”)
  359. 359. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“31”)
  360. 360. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“31”)
  361. 361. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“31”) validation
  362. 362. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“31”) validation
  363. 363. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”)
  364. 364. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”)
  365. 365. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”)
  366. 366. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”) validation
  367. 367. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”) validation
  368. 368. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”) Befriended(“34”)
  369. 369. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”) Befriended(“34”)
  370. 370. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”) Befriended(“34”)
  371. 371. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”) Befriended(“34”)
  372. 372. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”)
  373. 373. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List (“10”, “31”) Befriend(“34”) ACK
  374. 374. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List(“10”, “31”, “34”) Befriend(“34”)
  375. 375. Journal val id = “1” val firstName: String = “Jan” val lastName: String = “Kowalski” val friends: List[String] = List(“10”, “31”, “34”)
  376. 376. Let’s see some code!
  377. 377. What we are missing?
  378. 378. What we are missing? 1. Read-model
  379. 379. What we are missing? 1. Read-model 2. Validation
  380. 380. And that’s all folks!
  381. 381. Paweł Szulc
  382. 382. Paweł Szulc http://rabbitonweb.com
  383. 383. Paweł Szulc http://rabbitonweb.com @rabbitonweb
  384. 384. Paweł Szulc http://rabbitonweb.com @rabbitonweb https://github.com/rabbitonweb/
  385. 385. Thank you!

×