Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Demystifying Functional Programming

Demystifying Functional Programming

Avatar for dpsoft

dpsoft

July 17, 2018

More Decks by dpsoft

Other Decks in Technology

Transcript

  1. Typeclass Typeclass trait HtmlWriter[A] { def write(value: A): String }

    The type class itself is a trait with a single type parameter
  2. Typeclass Typeclass trait HtmlWriter[A] { def write(value: A): String }

    implicit val intWriter:HtmlWriter[Int] = new HtmlWriter[Int] { override def write(value: Int): String = value.toString } implicit val stringWriter:HtmlWriter[String] = new HtmlWriter[String] { override def write(value: String): String = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;") } The type class itself is a trait with a single type parameter Type class instances
  3. Typeclass Typeclass trait HtmlWriter[A] { def write(value: A): String }

    implicit val intWriter:HtmlWriter[Int] = new HtmlWriter[Int] { override def write(value: Int): String = value.toString } implicit val stringWriter:HtmlWriter[String] = new HtmlWriter[String] { override def write(value: String): String = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;") } implicit def boxWriter[A]:HtmlWriter[Box[A]] = new HtmlWriter[Box[A]] { override def write(value: Box[A]): String = ??? } The type class itself is a trait with a single type parameter Type class instances Extra type class instances
  4. Typeclass Typeclass trait HtmlWriter[A] { def write(value: A): String }

    implicit val intWriter:HtmlWriter[Int] = new HtmlWriter[Int] { override def write(value: Int): String = value.toString } implicit val stringWriter:HtmlWriter[String] = new HtmlWriter[String] { override def write(value: String): String = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;") } implicit def boxWriter[A]:HtmlWriter[Box[A]] = new HtmlWriter[Box[A]] { override def write(value: Box[A]): String = ??? } The type class itself is a trait with a single type parameter Type class instances Extra type class instances What are typeclasses? | Scala Haskell
  5. Semigroup Semigroup A semigroup is a binary associative operation trait

    Semigroup[A] { /** * Associativity means: * combine(x, combine(y, z)) = combine(combine(x, y), z) */ def combine(x: A, y: A): A // append | compose | whatever } implicit val intAdditionSemigroup: Semigroup[Int] = new Semigroup[Int] { override def combine(x: Int, y: Int): Int = x + y }
  6. Semigroup Semigroup A semigroup is a binary associative operation trait

    Semigroup[A] { /** * Associativity means: * combine(x, combine(y, z)) = combine(combine(x, y), z) */ def combine(x: A, y: A): A // append | compose | whatever } implicit val intAdditionSemigroup: Semigroup[Int] = new Semigroup[Int] { override def combine(x: Int, y: Int): Int = x + y } Semigroup.combineAll[Int](100, 50) //150 def combineAll[A](x: A, y: A)(implicit semigroup: Semigroup[A]): A = semigroup.combine(x, y)
  7. Semigroup Semigroup A semigroup is a binary associative operation trait

    Semigroup[A] { /** * Associativity means: * combine(x, combine(y, z)) = combine(combine(x, y), z) */ def combine(x: A, y: A): A // append | compose | whatever } implicit val intAdditionSemigroup: Semigroup[Int] = new Semigroup[Int] { override def combine(x: Int, y: Int): Int = x + y } Semigroup.combineAll[Int](100, 50) //150 List(1, 2, 3).foldLeft(0)(Semigroup.combineAll) //6 def combineAll[A](x: A, y: A)(implicit semigroup: Semigroup[A]): A = semigroup.combine(x, y)
  8. Semigroup Semigroup A semigroup is a binary associative operation trait

    Semigroup[A] { /** * Associativity means: * combine(x, combine(y, z)) = combine(combine(x, y), z) */ def combine(x: A, y: A): A // append | compose | whatever } implicit val intAdditionSemigroup: Semigroup[Int] = new Semigroup[Int] { override def combine(x: Int, y: Int): Int = x + y } Semigroup.combineAll[Int](100, 50) //150 List(1, 2, 3).foldLeft(0)(Semigroup.combineAll) //6 def reduce[A](as: List[A])(implicit semigroup: Semigroup[A]): A = as.foldLeft(/* ??? */)(semigroup.combine) // We can't generalize the previous function def combineAll[A](x: A, y: A)(implicit semigroup: Semigroup[A]): A = semigroup.combine(x, y)
  9. Monoid Monoid A monoid is a binary associative operation with

    an identity trait Monoid[A] extends Semigroup[A] { /** * Identity means: * combine(x, empty) = combine(empty, x) = x */ def empty: A } implicit val intMonoid:Monoid[Int] = new Monoid[Int] { def combine(x: Int, y: Int): Int = x + y def empty: Int = 0 }
  10. Monoid Monoid A monoid is a binary associative operation with

    an identity trait Monoid[A] extends Semigroup[A] { /** * Identity means: * combine(x, empty) = combine(empty, x) = x */ def empty: A } implicit val intMonoid:Monoid[Int] = new Monoid[Int] { def combine(x: Int, y: Int): Int = x + y def empty: Int = 0 } def combineAll[A](as: List[A])(implicit monoid: Monoid[A]): A = as.foldLeft(monoid.empty)(monoid.combine)
  11. Monoid Monoid A monoid is a binary associative operation with

    an identity trait Monoid[A] extends Semigroup[A] { /** * Identity means: * combine(x, empty) = combine(empty, x) = x */ def empty: A } implicit val intMonoid:Monoid[Int] = new Monoid[Int] { def combine(x: Int, y: Int): Int = x + y def empty: Int = 0 } def combineAll[A](as: List[A])(implicit monoid: Monoid[A]): A = as.foldLeft(monoid.empty)(monoid.combine) Monoid.combineAll(List(1,2,3))
  12. Monoid Monoid A monoid is a binary associative operation with

    an identity trait Monoid[A] extends Semigroup[A] { /** * Identity means: * combine(x, empty) = combine(empty, x) = x */ def empty: A } implicit val intMonoid:Monoid[Int] = new Monoid[Int] { def combine(x: Int, y: Int): Int = x + y def empty: Int = 0 } def combineAll[A](as: List[A])(implicit monoid: Monoid[A]): A = as.foldLeft(monoid.empty)(monoid.combine) Monoid.combineAll(List(1,2,3)) case class Times(data: Map[String, FiniteDuration]) implicit val timesMonoid: Monoid[Times] = new Monoid[Times] { def combine(x: Times, y: Times): Times = Times(x.data ++ y.data) def empty: Times = Times(Map.empty) } Example
  13. Functor Functor A functor is a way to apply a

    function over or around some structure that we don’t want to alter it
  14. Functor Functor trait Functor[F[_]] { /** * Identity: Option(identity(1)) ==

    identity(Option(1)) * Composition: F[f o g] = F[f] o F[g] */ def map[A, B](fa: F[A])(f: A => B): F[B] } implicit val optionFunctor: Functor[Option] = new Functor[Option] { def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa match { case None => None case Some(a) => Some(f(a)) } } A functor is a way to apply a function over or around some structure that we don’t want to alter it
  15. Functor Functor trait Functor[F[_]] { /** * Identity: Option(identity(1)) ==

    identity(Option(1)) * Composition: F[f o g] = F[f] o F[g] */ def map[A, B](fa: F[A])(f: A => B): F[B] } implicit val optionFunctor: Functor[Option] = new Functor[Option] { def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa match { case None => None case Some(a) => Some(f(a)) } } A functor is a way to apply a function over or around some structure that we don’t want to alter it def mapOption[A, B](as: Option[A]) (f: A => B) (implicit functor: Functor[Option]): Option[B] = functor.map(as)(f)
  16. Functor Functor Using a List implicit val listFunctor: Functor[List] =

    new Functor[List] { def map[A, B](fa: List[A])(f: A => B): List[B] = fa match { case Nil => Nil case a :: as => f(a) :: map(as)(f) } }
  17. Functor Functor Functor.map(List(2, 8, 12))(_ + 3) Using a List

    implicit val listFunctor: Functor[List] = new Functor[List] { def map[A, B](fa: List[A])(f: A => B): List[B] = fa match { case Nil => Nil case a :: as => f(a) :: map(as)(f) } } Example
  18. Functor Functor Functor.map(List(2, 8, 12))(_ + 3) Using a List

    implicit val listFunctor: Functor[List] = new Functor[List] { def map[A, B](fa: List[A])(f: A => B): List[B] = fa match { case Nil => Nil case a :: as => f(a) :: map(as)(f) } } Example
  19. Applicative Applicative Applicative is a way to apply a wrapped

    function over or around some structure that we don’t want to alter it
  20. Applicative Applicative Applicative is a way to apply a wrapped

    function over or around some structure that we don’t want to alter it trait Applicative[F[_]] extends Functor[F] { def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] def pure[A](a: A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa) }
  21. Applicative Applicative Applicative is a way to apply a wrapped

    function over or around some structure that we don’t want to alter it trait Applicative[F[_]] extends Functor[F] { def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] def pure[A](a: A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa) } implicit val optionApplicative = new Applicative[Option] { def ap[A, B](ff: Option[A => B])(fa: Option[A]): Option[B] = (ff, fa) match { case (Some(f), Some(a)) => Some(f(a)) case _ => None } def pure[A](a: A): Option[A] = Option(a) }
  22. Applicative Applicative Applicative is a way to apply a wrapped

    function over or around some structure that we don’t want to alter it trait Applicative[F[_]] extends Functor[F] { def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] def pure[A](a: A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa) } implicit val optionApplicative = new Applicative[Option] { def ap[A, B](ff: Option[A => B])(fa: Option[A]): Option[B] = (ff, fa) match { case (Some(f), Some(a)) => Some(f(a)) case _ => None } def pure[A](a: A): Option[A] = Option(a) } def apOption[A, B](opt: Option[A]) (f: Option[A => B]) (implicit functor: Applicative[Option]): Option[ optionApplicative.ap(f)(opt)
  23. Applicative Applicative apOption(Some(2))(Some(i => s"The number is: ${i.toString}")) // Some(The

    number is: 2) Example apOption(None)(Some(i => s"The number is: ${i.toString}")) // None
  24. Applicative Applicative apOption(Some(2))(Some(i => s"The number is: ${i.toString}")) // Some(The

    number is: 2) Example apOption(None)(Some(i => s"The number is: ${i.toString}")) // None
  25. Monad Monad Monad introduces a way to append two structure

    sequentially and the results of previous computations may influence what computations to run next
  26. Monad Monad Monad introduces a way to append two structure

    sequentially and the results of previous computations may influence what computations to run next trait Monad[F[_]] extends Applicative[F] { def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = flatMap(ff)(x => map(fa)(x)) }
  27. Monad Monad Monad introduces a way to append two structure

    sequentially and the results of previous computations may influence what computations to run next trait Monad[F[_]] extends Applicative[F] { def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = flatMap(ff)(x => map(fa)(x)) } val optionMonad = new Monad[Option] { def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = fa match { case None => None case Some(a) => f(a) } def pure[A](a: A): Option[A] = Some(a) }
  28. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example
  29. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None }
  30. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None }
  31. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  32. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  33. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  34. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  35. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  36. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  37. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  38. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  39. Monad Monad implicit class OptionMonad[A](opt: Option[A]) { def flatMap[B](f: A

    => Option[B]): Option[B] = optionMonad.flatMap(opt)(f) } Example def half(number: Int): Option[Int] = { if (number % 2 == 0) Some(number / 2) else None } val result = Some(6) .flatMap(half) .flatMap(half) .flatMap(half)
  40. Monad Transformers Monad Transformers def compose[M[_]: Monad, N[_]: Monad]: Monad[M[N[_]]]

    = Monads do not compose val greeting: Option[String] = Some("Hello") val firstName: Option[String] = Some("John") val lastName: Option[String] = Some("Doe") for { g <- greetingOption f <- firstNameOption l <- lastNameOption } yield s"$g $f $l" Same monads
  41. Monad Transformers Monad Transformers def compose[M[_]: Monad, N[_]: Monad]: Monad[M[N[_]]]

    = Monads do not compose val greetingFuture: Future[String] = Future.successful("Hello") val firstNameOption: Option[String] = Some("John") val lastNameOption: Option[String] = Some("Doe") for { g <- greetingFuture f <- firstNameOption l <- lastNameOption } yield s"$g $f $l" val greeting: Option[String] = Some("Hello") val firstName: Option[String] = Some("John") val lastName: Option[String] = Some("Doe") for { g <- greetingOption f <- firstNameOption l <- lastNameOption } yield s"$g $f $l" Same monads Different monads
  42. Monad Transformers Monad Transformers def compose[M[_]: Monad, N[_]: Monad]: Monad[M[N[_]]]

    = Monads do not compose val greetingFuture: Future[String] = Future.successful("Hello") val firstNameOption: Option[String] = Some("John") val lastNameOption: Option[String] = Some("Doe") for { g <- greetingFuture f <- firstNameOption l <- lastNameOption } yield s"$g $f $l" val greeting: Option[String] = Some("Hello") val firstName: Option[String] = Some("John") val lastName: Option[String] = Some("Doe") for { g <- greetingOption f <- firstNameOption l <- lastNameOption } yield s"$g $f $l" Same monads Different monads
  43. Monad Transformers Monad Transformers def compose[M[_]: Monad, N[_]: Monad]: Monad[M[N[_]]]

    = Monads do not compose val greetingFuture: Future[String] = Future.successful("Hello") val firstNameOption: Option[String] = Some("John") val lastNameOption: Option[String] = Some("Doe") for { g <- greetingFuture f <- firstNameOption l <- lastNameOption } yield s"$g $f $l" val greeting: Option[String] = Some("Hello") val firstName: Option[String] = Some("John") val lastName: Option[String] = Some("Doe") for { g <- greetingOption f <- firstNameOption l <- lastNameOption } yield s"$g $f $l" Same monads Monads (in general) do not compose Different monads
  44. Monad Transformers Monad Transformers Monads do not compose generically. Gives

    us a way to stack two monads on top of each other and have them act a single monad.
  45. Monad Transformers Monad Transformers Monads do not compose generically. Gives

    us a way to stack two monads on top of each other and have them act a single monad. EitherT[F[_], A, B] ^ |__ ANY MONAD!
  46. Monad Transformers Monad Transformers Monads do not compose generically. Gives

    us a way to stack two monads on top of each other and have them act a single monad. Future[Either[Throwable, String]] => EitherT[Future, Throwable, String] EitherT[F[_], A, B] ^ |__ ANY MONAD!
  47. Monad Transformers Monad Transformers Monads do not compose generically. Instead

    of nesting Gives us a way to stack two monads on top of each other and have them act a single monad. Future[Either[Throwable, String]] => EitherT[Future, Throwable, String] EitherT[F[_], A, B] ^ |__ ANY MONAD!
  48. Monad Transformers Monad Transformers Monads do not compose generically. Instead

    of nesting We have a stack Gives us a way to stack two monads on top of each other and have them act a single monad. Future[Either[Throwable, String]] => EitherT[Future, Throwable, String] EitherT[F[_], A, B] ^ |__ ANY MONAD!
  49. Monad Transformers Monad Transformers Monads do not compose generically. Instead

    of nesting We have a stack Gives us a way to stack two monads on top of each other and have them act a single monad. Future[Either[Throwable, String]] => EitherT[Future, Throwable, String] There exists a transformer equivalent for every monad instance Monad Transformers Monad Transformers | | Scala Scala Haskell Haskell EitherT[F[_], A, B] ^ |__ ANY MONAD!
  50. Monad Transformers Monad Transformers Example val greetingEither: Future[Either[Throwable, String]] =

    Future.successful(Either.right("Hello")) val firstNameFuture: Future[String] = Future.successful("Jane") val lastNameOption: Option[String] = Some("Doe") def greet: Future[Either[Throwable, String]] = { val result: EitherT[Future, Throwable, String] = for { g <- EitherT(greetingEither) f <- EitherT.liftF(firstNameFuture) l <- EitherT.fromOption[Future](lastNameOption, new RuntimeException("lastName not found")) } yield s"$g $f $l" result.value // => EitherT[Future, Throwable, String] => Future[Either[Throwable, String]] }
  51. Monad Transformers Monad Transformers Example val greetingEither: Future[Either[Throwable, String]] =

    Future.successful(Either.right("Hello")) val firstNameFuture: Future[String] = Future.successful("Jane") val lastNameOption: Option[String] = Some("Doe") def greet: Future[Either[Throwable, String]] = { val result: EitherT[Future, Throwable, String] = for { g <- EitherT(greetingEither) f <- EitherT.liftF(firstNameFuture) l <- EitherT.fromOption[Future](lastNameOption, new RuntimeException("lastName not found")) } yield s"$g $f $l" result.value // => EitherT[Future, Throwable, String] => Future[Either[Throwable, String]] }
  52. Monad Transformers Monad Transformers Example val greetingEither: Future[Either[Throwable, String]] =

    Future.successful(Either.right("Hello")) val firstNameFuture: Future[String] = Future.successful("Jane") val lastNameOption: Option[String] = Some("Doe") def greet: Future[Either[Throwable, String]] = { val result: EitherT[Future, Throwable, String] = for { g <- EitherT(greetingEither) f <- EitherT.liftF(firstNameFuture) l <- EitherT.fromOption[Future](lastNameOption, new RuntimeException("lastName not found")) } yield s"$g $f $l" result.value // => EitherT[Future, Throwable, String] => Future[Either[Throwable, String]] } case class AwesomeError(msg: String) type ResultT[F[_], A] = EitherT[F, AwesomeError, A] type FutureResult[A] = ResultT[Future, A]
  53. Monad Transformers Monad Transformers Example val greetingEither: Future[Either[Throwable, String]] =

    Future.successful(Either.right("Hello")) val firstNameFuture: Future[String] = Future.successful("Jane") val lastNameOption: Option[String] = Some("Doe") def greet: Future[Either[Throwable, String]] = { val result: EitherT[Future, Throwable, String] = for { g <- EitherT(greetingEither) f <- EitherT.liftF(firstNameFuture) l <- EitherT.fromOption[Future](lastNameOption, new RuntimeException("lastName not found")) } yield s"$g $f $l" result.value // => EitherT[Future, Throwable, String] => Future[Either[Throwable, String]] } case class AwesomeError(msg: String) type ResultT[F[_], A] = EitherT[F, AwesomeError, A] type FutureResult[A] = ResultT[Future, A] def getUser(id:String):FutureResult[User] def canBeUpdated(user:User):FutureResult[User] def update(user:User):FutureResult[User]
  54. Monad Transformers Monad Transformers Example val greetingEither: Future[Either[Throwable, String]] =

    Future.successful(Either.right("Hello")) val firstNameFuture: Future[String] = Future.successful("Jane") val lastNameOption: Option[String] = Some("Doe") def greet: Future[Either[Throwable, String]] = { val result: EitherT[Future, Throwable, String] = for { g <- EitherT(greetingEither) f <- EitherT.liftF(firstNameFuture) l <- EitherT.fromOption[Future](lastNameOption, new RuntimeException("lastName not found")) } yield s"$g $f $l" result.value // => EitherT[Future, Throwable, String] => Future[Either[Throwable, String]] } case class AwesomeError(msg: String) type ResultT[F[_], A] = EitherT[F, AwesomeError, A] type FutureResult[A] = ResultT[Future, A] def getUser(id:String):FutureResult[User] def canBeUpdated(user:User):FutureResult[User] def update(user:User):FutureResult[User] def updateUser(user: User):FutureResult[User] = for { u <- getUser(user.id) _ <- canBeUpdated(u) updatedUser <- updateUser(u) } yield updatedUser Better?
  55. Show me the Money! Show me the Money! Monad! Monad!

    trait UserStore { def findUser(id: Long): Future[Either[Throwable, User]] def canBeUpdated(user: User): Future[Either[Throwable, Boolean]] def updateUser(user: User)(pointsToAdd: Int): Future[Either[Throwable, User]] } class LoyaltyPoints(store: UserStore) { def addPoints(userId: Long, pointsToAdd: Int): Future[Either[Throwable, User]] = { val result: Future[Either[Throwable, User]] = for { user <- store.findUser(userId) _ <- user.right.map(store.canBeUpdated).left.map(Future.failed).merge updatedUser <- user.right.map(store.updateUser(_)(pointsToAdd)).left.map(Future.failed). } yield updatedUser result } } Api V0
  56. Show me the Money! Show me the Money! Monad! Monad!

    trait UserStore { def findUser(id: Long): Future[Either[Throwable, User]] def canBeUpdated(user: User): Future[Either[Throwable, Boolean]] def updateUser(user: User)(pointsToAdd: Int): Future[Either[Throwable, User]] } class LoyaltyPoints(store: UserStore) { def addPoints(userId: Long, pointsToAdd: Int): Future[Either[Throwable, User]] = { val result: Future[Either[Throwable, User]] = for { user <- store.findUser(userId) _ <- user.right.map(store.canBeUpdated).left.map(Future.failed).merge updatedUser <- user.right.map(store.updateUser(_)(pointsToAdd)).left.map(Future.failed). } yield updatedUser result } } Api V0
  57. Show me the Monad! Show me the Monad! trait UserStore

    { def findUser(id: Long): Future[Either[Throwable, User]] def canBeUpdated(user: User): Future[Either[Throwable, Boolean]] def updateUser(user: User)(pointsToAdd: Int): Future[Either[Throwable, User]] } class LoyaltyPoints(store: UserStore) { def addPoints(userId: Long, pointsToAdd: Int): Future[Either[Throwable, User]] = { val result: EitherT[Future, Throwable, User] = for { user <- EitherT(store.findUser(userId)) _ <- EitherT(store.canBeUpdated(user)) updatedUser <- EitherT(store.updateUser(user)(pointsToAdd)) } yield updatedUser result.value // EitherT[Future, Throwable, User] => Future[Either[Throwable, User]] } } Api V1
  58. Show me the Monad! Show me the Monad! trait UserStore

    { def findUser(id: Long): Future[Either[Throwable, User]] def canBeUpdated(user: User): Future[Either[Throwable, Boolean]] def updateUser(user: User)(pointsToAdd: Int): Future[Either[Throwable, User]] } class LoyaltyPoints(store: UserStore) { def addPoints(userId: Long, pointsToAdd: Int): Future[Either[Throwable, User]] = { val result: EitherT[Future, Throwable, User] = for { user <- EitherT(store.findUser(userId)) _ <- EitherT(store.canBeUpdated(user)) updatedUser <- EitherT(store.updateUser(user)(pointsToAdd)) } yield updatedUser result.value // EitherT[Future, Throwable, User] => Future[Either[Throwable, User]] } } Api V1 So far so good, but what about description and interpretation?
  59. Show me the Monad! Show me the Monad! trait UserStoreAlg[F[_]]

    { def findUser(id: Long): F[Either[Throwable, User]] def canBeUpdated(user: User): F[Either[Throwable, Boolean]] def updateUser(user: User)(pointsToAdd: Int): F[Either[Throwable, User]] } class LoyaltyPoints[F[_]](store: UserStoreAlg[F])(implicit M: Monad[F]) { def addPoints(userId: Long, pointsToAdd: Int): F[Either[Throwable, User]] = { val result: EitherT[F, Throwable, User] = for { user <- EitherT(store.findUser(userId)) _ <- EitherT(store.canBeUpdated(user)) updatedUser <- EitherT(store.updateUser(user)(pointsToAdd)) } yield updatedUser result.value // EitherT[F, Throwable, User] => F[Either[Throwable, User]] } } Api v2
  60. Show me the Monad! Show me the Monad! trait UserStoreAlg[F[_]]

    { def findUser(id: Long): F[Either[Throwable, User]] def canBeUpdated(user: User): F[Either[Throwable, Boolean]] def updateUser(user: User)(pointsToAdd: Int): F[Either[Throwable, User]] } class LoyaltyPoints[F[_]](store: UserStoreAlg[F])(implicit M: Monad[F]) { def addPoints(userId: Long, pointsToAdd: Int): F[Either[Throwable, User]] = { val result: EitherT[F, Throwable, User] = for { user <- EitherT(store.findUser(userId)) _ <- EitherT(store.canBeUpdated(user)) updatedUser <- EitherT(store.updateUser(user)(pointsToAdd)) } yield updatedUser result.value // EitherT[F, Throwable, User] => F[Either[Throwable, User]] } } Api v2 so, it smells like we have to put EitherT everywhere, doesn't it?
  61. Show me the Monad! Show me the Monad! case class

    ServiceError(msg: String) type ResultOrError[F[_], A] = EitherT[F, ServiceError, A] trait UserStoreAlg[F[_]] { def findUser(id: Long): ResultOrError[F, User] def canBeUpdated(user: User): ResultOrError[F, Boolean] def updateUser(user: User)(pointsToAdd: Int): ResultOrError[F, User] } class LoyaltyPoints[F[_]](store: UserStoreAlg[F])(implicit M: Monad[F]) { def addPoints(userId: Long, pointsToAdd: Int): F[Either[ServiceError, User]] = { val result: ResultOrError[F, User] = for { user <- store.findUser(userId) _ <- store.canBeUpdated(user) updatedUser <- store.updateUser(user)(pointsToAdd) } yield updatedUser result.value // EitherT[F, ServiceError, User] => F[Either[ServiceError, User]] } } Api v3
  62. Show me the Monad! Show me the Monad! case class

    ServiceError(msg: String) type ResultOrError[F[_], A] = EitherT[F, ServiceError, A] trait UserStoreAlg[F[_]] { def findUser(id: Long): ResultOrError[F, User] def canBeUpdated(user: User): ResultOrError[F, Boolean] def updateUser(user: User)(pointsToAdd: Int): ResultOrError[F, User] } class LoyaltyPoints[F[_]](store: UserStoreAlg[F])(implicit M: Monad[F]) { def addPoints(userId: Long, pointsToAdd: Int): F[Either[ServiceError, User]] = { val result: ResultOrError[F, User] = for { user <- store.findUser(userId) _ <- store.canBeUpdated(user) updatedUser <- store.updateUser(user)(pointsToAdd) } yield updatedUser result.value // EitherT[F, ServiceError, User] => F[Either[ServiceError, User]] } } Api v3 and now to run the code...
  63. Show me the Monad! Show me the Monad! trait FutureInterpreter

    extends UserStoreAlg[Future] { override def findUser(id: Long): EitherT[Future, ServiceError, User] = EitherT(Future.successful(Either.right(User(1L, "[email protected]", 1000)))) override def canBeUpdated(user: User): ResultOrError[Future, Boolean] = EitherT(Future.successful(Either.right(true))) override def updateUser(user: User)(pointsToAdd: Int): ResultOrError[Future, User] = EitherT(Future.successful( Either.right(user.copy(loyaltyPoints = user.loyaltyPoints + pointsToAdd)))) } The Interpreter
  64. Show me the Monad! Show me the Monad! trait FutureInterpreter

    extends UserStoreAlg[Future] { override def findUser(id: Long): EitherT[Future, ServiceError, User] = EitherT(Future.successful(Either.right(User(1L, "[email protected]", 1000)))) override def canBeUpdated(user: User): ResultOrError[Future, Boolean] = EitherT(Future.successful(Either.right(true))) override def updateUser(user: User)(pointsToAdd: Int): ResultOrError[Future, User] = EitherT(Future.successful( Either.right(user.copy(loyaltyPoints = user.loyaltyPoints + pointsToAdd)))) } The Interpreter val interpreter:FutureInterpreter = new FutureInterpreter{} val result: Future[Either[ServiceError, User]] = new LoyaltyPoints(interpreter) .addPoints(1, 100) // wait the future Await.result(result, 1 second).foreach(println) // User(1,[email protected],1100)
  65. FP in the JVM FP in the JVM Scalaz -

    scala Cats- scala Arrow- kotlin Cyclops-react - java vavr.io - java
  66. Resources Resources Functors, Applicatives and Monads In Pictures Functional Programming

    in Scala Learn You a Haskell for Great Good! Haskell Programming from first principles Cats Scalaz Category Theory for Programmers Demystifying Functional Programming (code examples)