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.
[error] Exception encountered
[error] java.lang.StackOverflowError
WHY THE FREE MONAD ISN’T FREE
“Let’s just trampoline it and
add the Free Monad”
@kelleyrobinson
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
“Let’s just trampoline it and
add the Free Monad”
Why The Free
Monad Isn’t Free
Kelley Robinson
Data & Infrastructure Engineer
Sharethrough
@kelleyrobinson
WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real...
WHY THE FREE MONAD ISN’T FREE
github.com/robinske/monad-examples
WHY THE FREE MONAD ISN’T FREE
https://twitter.com/rickasaurus/status/705134684427128833
WHY THE FREE MONAD ISN’T FREE
Monoids
@kelleyrobinson
@kelleyrobinson
trait Monoid[A] {


def append(a: A, b: A): A


def identity: A

}
WHY THE FREE MONAD ISN'T FREE
Monoids
Image credit: deluxebattery.com
WHY THE FREE MONAD ISN'T FREE
Properties
Identity: "no-op" value
Associativity:
grouping doesn't matter
@kelleyrobinson
@kelleyrobinson
object StringConcat extends Monoid[String] {



def append(a: String, b: String): String = a + b



def id...
@kelleyrobinson
object IntegerAddition extends Monoid[Int] {



def append(a: Int, b: Int): Int = a + b



def identity: I...
@kelleyrobinson
object IntegerMultiplication extends Monoid[Int] {



def append(a: Int, b: Int): Int = a * b



def ident...
@kelleyrobinson
object FunctionComposition /* extends Monoid[_=>_] */ {



def append[A, B, C](f1: A => B, f2: B => C): A ...
@kelleyrobinson
object FunctionComposition /* extends Monoid[_=>_] */ {



def append[A, B, C](f1: A => B, f2: B => C): A ...
WHY THE FREE MONAD ISN’T FREE
Functors
@kelleyrobinson
@kelleyrobinson
trait Functor[F[_]] {



def map[A, B](a: F[A])(fn: A => B): F[B]



}
WHY THE FREE MONAD ISN'T FREE
@kelleyrobinson
Properties
Identity: "no-op" value
Composition:
grouping doesn't matter
@kelleyrobinson
sealed trait Option[+A]

case class Some[A](a: A) extends Option[A]

case object None extends Option[Nothi...
@kelleyrobinson
it("should follow the identity law") {

def identity[A](a: A): A = a

assert(map(Some("foo"))(identity) ==...
@kelleyrobinson
it("should follow the composition law") {
val f: String => String = s => s + "a"

val g: String => String ...
Functors are Endofunctors**
**in Scala
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
WHY THE FREE MONAD ISN’T FREE
Monads
@kelleyrobinson
"The term monad is a bit
vacuous if you are not a
mathematician. An alternative
term is computation builder."
WHY THE FREE...
@kelleyrobinson
trait Monad[M[_]] {


def pure[A](a: A): M[A]

def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]

}
@kelleyrobinson
sealed trait Option[+A]

case class Some[A](a: A) extends Option[A]

case object None extends Option[Nothi...
@kelleyrobinson
trait Monad[M[_]] {


def pure[A](a: A): M[A]

def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]

}
@kelleyrobinson
trait Monad[M[_]] {

def pure[A](a: A): M[A]

def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]



def map[A...
@kelleyrobinson
trait Monad[M[_]] {
def pure[A](a: A): M[A]

def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]



def map[A,...
@kelleyrobinson
trait Monad[M[_]] {

def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]

def append[A, B, C]
(f1: A => M[B], ...
@kelleyrobinson
trait Monad[M[_]] {

def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]

def append[A, B, C]
(f1: A => M[B], ...
WHY THE FREE MONAD ISN'T FREE
Properties
Identity: "no-op" value
Composition:
grouping doesn't matter
@kelleyrobinson
WHY THE FREE MONAD ISN'T FREE
Compose functions
for values in a
context
Think: Lists, Options, Futures
@kelleyrobinson
trait Monad[M[_]]
extends Functor[M]
/* with Monoid[_=>M[_]] */ {


def pure[A](a: A): M[A]

def flatMap[A, B](a: M[A])(fn...
@kelleyrobinson
trait Monad[M[_]]
extends Functor[M]
/* with Monoid[ _ => M[_] ] */ {


def pure[A](a: A): M[A]

def flatM...
@kelleyrobinson
object FunctionComposition /* extends Monoid[_ => _] */{



...
}
trait Monad[M[_]] /* extends Monoid[_ =>...
WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real...
WHY THE FREE MONAD ISN'T FREE
The word "free" is
used in the sense of
"unrestricted" rather
than "zero-cost"
$
@kelleyrobi...
WHY THE FREE MONAD ISN'T FREE
"Freedom not beer"
https://en.wikipedia.org/wiki/Gratis_versus_libre#/media/File:Galuel_RMS_...
WHY THE FREE MONAD ISN’T FREE
Free Monoids
@kelleyrobinson
@kelleyrobinson
trait Monoid[A] {


def append(a: A, b: A): A

def identity: A

}
WHY THE FREE MONAD ISN’T FREE
Free Monoids
• Free from interpretation
• No lost input data when
appending
@kelleyrobinson
...
@kelleyrobinson
// I'm free!
class ListConcat[A] extends Monoid[List[A]] {

def append(a: List[A], b: List[A]): List[A] =
...
@kelleyrobinson
// I'm not free :(
object IntegerAddition extends Monoid[Int] {



def append(a: Int, b: Int): Int = a + b...
WHY THE FREE MONAD ISN’T FREE
Free Monads
@kelleyrobinson
Don't lose any data!
(that means no evaluating functions)
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
@kelleyrobinson
def notFreeAppend[A, B, C]
(f1: A => M[B], f2: B => M[C]): A => M[C] = {
a: A =>

// evaluate f1
val bs: M...
@kelleyrobinson
sealed trait Free[F[_], A] { self =>



}

@kelleyrobinson
sealed trait Free[F[_], A] { self =>

}



case class Return[F[_], A](given: A) extends Free[F, A]
@kelleyrobinson
sealed trait Free[F[_], A] { self =>



}



case class Return[F[_], A](given: A) extends Free[F, A]
case ...
@kelleyrobinson
sealed trait Free[F[_], A] { self =>



}



case class Return[F[_], A](given: A) extends Free[F, A]
case ...
@kelleyrobinson
sealed trait Free[F[_], A] { self =>
def flatMap ...
def pure ...
def map ...
}



case class Return[F[_],...
@kelleyrobinson
sealed trait Todo[A]

case class NewTask[A](task: A) extends Todo[A]

case class CompleteTask[A](task: A) ...
@kelleyrobinson
val todos: Free[Todo, Map[String, Boolean]] =

for {

_ <- newTask("Go to scala days")

_ <- newTask("Writ...
@kelleyrobinson
val todosExpanded: Free[Todo, Map[String, Boolean]] =

FlatMap(

Suspend(NewTask("Go to scala days")), (a:...
WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real...
WHY THE FREE MONAD ISN'T FREE
What's the point?
• Defer side effects
• Multiple interpreters
• Stack safety
@kelleyrobinson
@kelleyrobinson


(1 to 1000).flatMap { i =>

doSomething(i).flatMap { j =>

doSomethingElse(j).flatMap { k =>

doAnotherT...
WHY THE FREE MONAD ISN’T FREE
“Let’s just trampoline it and
add the Free Monad”
@kelleyrobinson
WHY THE FREE MONAD ISN’T FREE
Trampolining
Express it in a loop
@kelleyrobinson
WHY THE FREE MONAD ISN’T FREE
The Free Monad
uses heap instead of
using stack.
@kelleyrobinson
@kelleyrobinson
val todosExpanded: Free[Todo, Map[String, Boolean]] =

FlatMap(

Suspend(NewTask("Go to scala days")), (a:...
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Evaluating
Use a loop
@kelleyrobinson
def runFree[F[_], G[_], A]
(f: Free[F, A])
(transform: FunctorTransformer[F, G])
(implicit G: Monad[G]): G...
@kelleyrobinson
def runFree[F[_], G[_], A]
(f: Free[F, A])
(transform: FunctorTransformer[F, G])
(implicit G: Monad[G]): G...
@kelleyrobinson
// or 'NaturalTransformation'

trait FunctorTransformer[F[_], G[_]] {

def apply[A](f: F[A]): G[A]

}

// ...
@kelleyrobinson
/* Function body */
@annotation.tailrec

def tailThis(free: Free[F, A]): Free[F, A] = free match {

case F...
@kelleyrobinson
tailThis(f) match {

case Return(a) => ...

case Suspend(fa) => transform(fa)
case FlatMap(Suspend(fa), fn...
@kelleyrobinson
def runLoop[F[_], G[_], A](...): G[A] = {

var eval: Free[F, A] = f



while (true) {

eval match {

case ...
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Evaluating
Applies transformation on
`Suspend`
Trampolining for stack safety
@kelleyrobinson
// or 'NaturalTransformation'

trait FunctorTransformer[F[_], G[_]] {

def apply[A](f: F[A]): G[A]

}

// ...
@kelleyrobinson
type Id[A] = A
case class TestEvaluator(var model: Map[String, Boolean])
extends FunctorTransformer[Todo, ...
@kelleyrobinson
a match {
case NewTask(task) =>

model = model + (task.toString -> false)

task

case CompleteTask(task) =...
@kelleyrobinson
it("should evaluate todos") {

val result =
runFree(todos)(TestEvaluator(Map.empty))

val expected: Map[St...
@kelleyrobinson
case object ActionTestEvaluator
extends FunctorTransformer[Todo, Id] {


var actions: List[Todo[String]] =...
@kelleyrobinson
a match {
case NewTask(task) =>

actions = actions :+ NewTask(task.toString)

task

case CompleteTask(task...
@kelleyrobinson
it("should evaluate todos actions in order") {

runFree(todos)(ActionTestEvaluator)



val expected: List[...
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Defining multiple interpreters allows
you to test side-effecting code withou...
@kelleyrobinson
// Production Interpreter
def apply[A](a: Todo[A]): Option[A] = {

a match {

case NewTask(task) =>

/**
*...
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Justifications
• Defer side effects
• Multiple interpreters
• Stack safety
WHY THE FREE MONAD ISN'T FREE
#BlueSkyScala
The path to learning is broken
@kelleyrobinson
Credit: Jessica Kerr
WHY THE FREE MONAD ISN'T FREE
Freedom isn't free
Reasons to avoid the Free Monad
• Boilerplate
• Learning curve
• Alternat...
WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real...
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Know your domain
WHY THE FREE MONAD ISN'T FREE
Functional Spectrum
Where does your team fall?
Java Haskell
WHY THE FREE MONAD ISN'T FREE
Functional Spectrum
Where does your team fall?
Java Haskell
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Alternatives for
maintaining stack
safety
@kelleyrobinson
final override def map[B, That](f: A => B)
(implicit bf: CanBuildFrom[List[A], B, That]): That = {

if (bf...
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Alternatives for
managing side
effects
@kelleyrobinson
import java.sql.ResultSet



case class Person(name: String, age: Int)



def getPerson(rs: ResultSet): Pe...
@kelleyrobinson
def handleFailure[A](f: => A): ActionResult / A = {

Try(f) match {

case Success(res) => res.right

case ...
WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real...
WHY THE FREE MONAD ISN'T FREE
Scalaz
Scalaz is a Scala library for functional programming.
http://scalaz.github.io/scalaz/...
WHY THE FREE MONAD ISN'T FREE
Examples
• Doobie
• scalaz.concurrent.Task
@kelleyrobinson
https://github.com/tpolecat/doobie
@kelleyrobinson
import scalaz.concurrent.Task
def apply(conf: Config, messages: List[SQSMessage]): Unit = {

val tasks = m...
@kelleyrobinson
// yikes
object Task {

implicit val taskInstance:
Nondeterminism[Task] with Catchable[Task]
with MonadErr...
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
My experience...
...what happened?
WHY THE FREE MONAD ISN’T FREE
- Know your domain
- Use clean abstractions
- Share knowledge
$
@kelleyrobinson
Thank You!
@kelleyrobinson
hello@krobinson.me
WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Acknowledgements & Resources
Special thanks to:
• Sharethrough
• Rúnar Bjarn...
Why The Free Monad isn't Free
Why The Free Monad isn't Free
Upcoming SlideShare
Loading in …5
×

Why The Free Monad isn't Free

12,879 views

Published on

This talk discusses the price of implementing the Free Monad in your code - spoiler alert - it's not free.

Published in: Engineering

Why The Free Monad isn't Free

  1. 1. [error] Exception encountered [error] java.lang.StackOverflowError
  2. 2. WHY THE FREE MONAD ISN’T FREE “Let’s just trampoline it and add the Free Monad” @kelleyrobinson
  3. 3. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson “Let’s just trampoline it and add the Free Monad”
  4. 4. Why The Free Monad Isn’t Free Kelley Robinson Data & Infrastructure Engineer Sharethrough @kelleyrobinson
  5. 5. WHY THE FREE MONAD ISN’T FREE - Monoids, Functors & Monads - How to be “Free” - Why & Why Not “Free” - Alternatives - Real World Applications $ @kelleyrobinson
  6. 6. WHY THE FREE MONAD ISN’T FREE github.com/robinske/monad-examples
  7. 7. WHY THE FREE MONAD ISN’T FREE https://twitter.com/rickasaurus/status/705134684427128833
  8. 8. WHY THE FREE MONAD ISN’T FREE Monoids @kelleyrobinson
  9. 9. @kelleyrobinson trait Monoid[A] { 
 def append(a: A, b: A): A 
 def identity: A
 }
  10. 10. WHY THE FREE MONAD ISN'T FREE Monoids Image credit: deluxebattery.com
  11. 11. WHY THE FREE MONAD ISN'T FREE Properties Identity: "no-op" value Associativity: grouping doesn't matter @kelleyrobinson
  12. 12. @kelleyrobinson object StringConcat extends Monoid[String] {
 
 def append(a: String, b: String): String = a + b
 
 def identity: String = ""
 
 }
  13. 13. @kelleyrobinson object IntegerAddition extends Monoid[Int] {
 
 def append(a: Int, b: Int): Int = a + b
 
 def identity: Int = 0
 
 }
  14. 14. @kelleyrobinson object IntegerMultiplication extends Monoid[Int] {
 
 def append(a: Int, b: Int): Int = a * b
 
 def identity: Int = 1
 
 }
  15. 15. @kelleyrobinson object FunctionComposition /* extends Monoid[_=>_] */ {
 
 def append[A, B, C](f1: A => B, f2: B => C): A => C = (a: A) => f2(f1(a))
 
 def identity[A]: A => A = (a: A) => a 
 }
  16. 16. @kelleyrobinson object FunctionComposition /* extends Monoid[_=>_] */ {
 
 def append[A, B, C](f1: A => B, f2: B => C): A => C = (a: A) => f2(f1(a))
 
 def identity[A]: A => A = (a: A) => a 
 }
  17. 17. WHY THE FREE MONAD ISN’T FREE Functors @kelleyrobinson
  18. 18. @kelleyrobinson trait Functor[F[_]] {
 
 def map[A, B](a: F[A])(fn: A => B): F[B]
 
 }
  19. 19. WHY THE FREE MONAD ISN'T FREE @kelleyrobinson Properties Identity: "no-op" value Composition: grouping doesn't matter
  20. 20. @kelleyrobinson sealed trait Option[+A]
 case class Some[A](a: A) extends Option[A]
 case object None extends Option[Nothing] object OptionFunctor extends Functor[Option] {
 
 def map[A, B](a: Option[A])(fn: A => B): Option[B] =
 a match {
 case Some(something) => Some(fn(something))
 case None => None
 }
 }
  21. 21. @kelleyrobinson it("should follow the identity law") {
 def identity[A](a: A): A = a
 assert(map(Some("foo"))(identity) == Some("foo"))
 }
  22. 22. @kelleyrobinson it("should follow the composition law") { val f: String => String = s => s + "a"
 val g: String => String = s => s + "l" val h: String => String = s => s + "a" assert( map(Some("sc"))(f andThen g andThen h) == map(map(map(Some("sc"))(f))(g))(h) == "scala" ) }
  23. 23. Functors are Endofunctors** **in Scala WHY THE FREE MONAD ISN’T FREE @kelleyrobinson
  24. 24. WHY THE FREE MONAD ISN’T FREE Monads @kelleyrobinson
  25. 25. "The term monad is a bit vacuous if you are not a mathematician. An alternative term is computation builder." WHY THE FREE MONAD ISN’T FREE @kelleyrobinson http://stackoverflow.com/questions/44965/what-is-a-monad
  26. 26. @kelleyrobinson trait Monad[M[_]] { 
 def pure[A](a: A): M[A]
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 }
  27. 27. @kelleyrobinson sealed trait Option[+A]
 case class Some[A](a: A) extends Option[A]
 case object None extends Option[Nothing]
 
 object OptionMonad extends Monad[Option] {
 
 def pure[A](a: A): Option[A] = Some(a)
 
 def flatMap[A, B](a: Option[A])(fn: A => Option[B]): Option[B] =
 a match {
 case Some(something) => fn(something)
 case None => None
 }
 }
  28. 28. @kelleyrobinson trait Monad[M[_]] { 
 def pure[A](a: A): M[A]
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 }
  29. 29. @kelleyrobinson trait Monad[M[_]] {
 def pure[A](a: A): M[A]
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 
 def map[A, B](a: M[A])(fn: A => B): M[B] = {
 flatMap(a){ b: A => pure(fn(b)) }
 } 
 }
  30. 30. @kelleyrobinson trait Monad[M[_]] { def pure[A](a: A): M[A]
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 
 def map[A, B](a: M[A])(fn: A => B): M[B] = {
 flatMap(a){ b: A => pure(fn(b)) }
 }
 }
  31. 31. @kelleyrobinson trait Monad[M[_]] {
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 def append[A, B, C] (f1: A => M[B], f2: B => M[C]): A => M[C] = { a: A =>
 val bs: M[B] = f1(a)
 val cs: M[C] = flatMap(bs) { b: B => f2(b) }
 cs
 } 
 }
  32. 32. @kelleyrobinson trait Monad[M[_]] {
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 def append[A, B, C] (f1: A => M[B], f2: B => M[C]): A => M[C] = { a: A =>
 val bs: M[B] = f1(a)
 val cs: M[C] = flatMap(bs) { b: B => f2(b) }
 cs
 } 
 }
  33. 33. WHY THE FREE MONAD ISN'T FREE Properties Identity: "no-op" value Composition: grouping doesn't matter @kelleyrobinson
  34. 34. WHY THE FREE MONAD ISN'T FREE Compose functions for values in a context Think: Lists, Options, Futures @kelleyrobinson
  35. 35. trait Monad[M[_]] extends Functor[M] /* with Monoid[_=>M[_]] */ { 
 def pure[A](a: A): M[A]
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 
 def map[A, B](a: M[A])(fn: A => B): M[B]
 def append[A, B, C](f1: A => M[B], f2: B => M[C]): A => M[C]
 def identity[A]: A => M[A]
 } @kelleyrobinson trait Monad[M[_]] extends Functor[M] /* with Monoid[ _ => M[_] ] */ { 
 def pure[A](a: A): M[A]
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 
 def map[A, B](a: M[A])(fn: A => B): M[B]
 def append[A, B, C](f1: A => M[B], f2: B => M[C]): A => M[C]
 def identity[A]: A => M[A]
 }
  36. 36. @kelleyrobinson trait Monad[M[_]] extends Functor[M] /* with Monoid[ _ => M[_] ] */ { 
 def pure[A](a: A): M[A]
 def flatMap[A, B](a: M[A])(fn: A => M[B]): M[B]
 
 def map[A, B](a: M[A])(fn: A => B): M[B]
 def append[A, B, C](f1: A => M[B], f2: B => M[C]): A => M[C]
 def identity[A]: A => M[A]
 }
  37. 37. @kelleyrobinson object FunctionComposition /* extends Monoid[_ => _] */{
 
 ... } trait Monad[M[_]] /* extends Monoid[_ => M[_]] */{ 
 ... }
  38. 38. WHY THE FREE MONAD ISN’T FREE - Monoids, Functors & Monads - How to be “Free” - Why & Why Not “Free” - Alternatives - Real World Applications $ @kelleyrobinson
  39. 39. WHY THE FREE MONAD ISN'T FREE The word "free" is used in the sense of "unrestricted" rather than "zero-cost" $ @kelleyrobinson
  40. 40. WHY THE FREE MONAD ISN'T FREE "Freedom not beer" https://en.wikipedia.org/wiki/Gratis_versus_libre#/media/File:Galuel_RMS_-_free_as_free_speech,_not_as_free_beer.png
  41. 41. WHY THE FREE MONAD ISN’T FREE Free Monoids @kelleyrobinson
  42. 42. @kelleyrobinson trait Monoid[A] { 
 def append(a: A, b: A): A
 def identity: A
 }
  43. 43. WHY THE FREE MONAD ISN’T FREE Free Monoids • Free from interpretation • No lost input data when appending @kelleyrobinson image credit: http://celestemorris.com
  44. 44. @kelleyrobinson // I'm free! class ListConcat[A] extends Monoid[List[A]] {
 def append(a: List[A], b: List[A]): List[A] = a ++ b
 def identity: List[A] = List.empty[A]
 }
  45. 45. @kelleyrobinson // I'm not free :( object IntegerAddition extends Monoid[Int] {
 
 def append(a: Int, b: Int): Int = a + b
 
 def identity: Int = 0
 
 }
  46. 46. WHY THE FREE MONAD ISN’T FREE Free Monads @kelleyrobinson
  47. 47. Don't lose any data! (that means no evaluating functions) WHY THE FREE MONAD ISN’T FREE @kelleyrobinson
  48. 48. @kelleyrobinson def notFreeAppend[A, B, C] (f1: A => M[B], f2: B => M[C]): A => M[C] = { a: A =>
 // evaluate f1 val bs: M[B] = f1(a) // evaluate f2
 val cs: M[C] = flatMap(bs) { b: B => f2(b) }
 cs }
  49. 49. @kelleyrobinson sealed trait Free[F[_], A] { self =>
 
 }

  50. 50. @kelleyrobinson sealed trait Free[F[_], A] { self =>
 }
 
 case class Return[F[_], A](given: A) extends Free[F, A]
  51. 51. @kelleyrobinson sealed trait Free[F[_], A] { self =>
 
 }
 
 case class Return[F[_], A](given: A) extends Free[F, A] case class Suspend[F[_], A](fn: F[A]) extends Free[F, A]

  52. 52. @kelleyrobinson sealed trait Free[F[_], A] { self =>
 
 }
 
 case class Return[F[_], A](given: A) extends Free[F, A] case class Suspend[F[_], A](fn: F[A]) extends Free[F, A]
 case class FlatMap[F[_], A, B] (free: Free[F, A], fn: A => Free[F, B]) extends Free[F, B]
  53. 53. @kelleyrobinson sealed trait Free[F[_], A] { self => def flatMap ... def pure ... def map ... }
 
 case class Return[F[_], A](given: A) extends Free[F, A] case class Suspend[F[_], A](fn: F[A]) extends Free[F, A]
 case class FlatMap[F[_], A, B] (free: Free[F, A], fn: A => Free[F, B]) extends Free[F, B]
  54. 54. @kelleyrobinson sealed trait Todo[A]
 case class NewTask[A](task: A) extends Todo[A]
 case class CompleteTask[A](task: A) extends Todo[A]
 case class GetTasks[A](default: A) extends Todo[A] def newTask[A](task: A): Free[Todo, A] = Suspend(NewTask(task))
 def completeTask[A](task: A): Free[Todo, A] = Suspend(CompleteTask(task))
 def getTasks[A](default: A): Free[Todo, A] = Suspend(GetTasks(default))
  55. 55. @kelleyrobinson val todos: Free[Todo, Map[String, Boolean]] =
 for {
 _ <- newTask("Go to scala days")
 _ <- newTask("Write a novel")
 _ <- newTask("Meet Tina Fey")
 _ <- completeTask("Go to scala days")
 tsks <- getTasks(Map.empty)
 } yield tsks
  56. 56. @kelleyrobinson val todosExpanded: Free[Todo, Map[String, Boolean]] =
 FlatMap(
 Suspend(NewTask("Go to scala days")), (a: String) =>
 FlatMap(
 Suspend(NewTask("Write a novel")), (b: String) =>
 FlatMap(
 Suspend(NewTask("Meet Tina Fey")), (c: String) =>
 FlatMap(
 Suspend(CompleteTask("Go to scala days")), (d: String) => Suspend(GetTasks(default = Map.empty))
 )
 )
 )
 )
  57. 57. WHY THE FREE MONAD ISN’T FREE - Monoids, Functors & Monads - How to be “Free” - Why & Why Not “Free” - Alternatives - Real World Applications $ @kelleyrobinson
  58. 58. WHY THE FREE MONAD ISN'T FREE What's the point? • Defer side effects • Multiple interpreters • Stack safety @kelleyrobinson
  59. 59. @kelleyrobinson 
 (1 to 1000).flatMap { i =>
 doSomething(i).flatMap { j =>
 doSomethingElse(j).flatMap { k =>
 doAnotherThing(k).map { l =>
 ...
  60. 60. WHY THE FREE MONAD ISN’T FREE “Let’s just trampoline it and add the Free Monad” @kelleyrobinson
  61. 61. WHY THE FREE MONAD ISN’T FREE Trampolining Express it in a loop @kelleyrobinson
  62. 62. WHY THE FREE MONAD ISN’T FREE The Free Monad uses heap instead of using stack. @kelleyrobinson
  63. 63. @kelleyrobinson val todosExpanded: Free[Todo, Map[String, Boolean]] =
 FlatMap(
 Suspend(NewTask("Go to scala days")), (a: String) =>
 FlatMap(
 Suspend(NewTask("Write a novel")), (b: String) =>
 FlatMap(
 Suspend(NewTask("Meet Tina Fey")), (c: String) =>
 FlatMap(
 Suspend(CompleteTask("Go to scala days")), (d: String) => Suspend(GetTasks(default = Map.empty))
 )
 )
 )
 )
  64. 64. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson Evaluating Use a loop
  65. 65. @kelleyrobinson def runFree[F[_], G[_], A] (f: Free[F, A]) (transform: FunctorTransformer[F, G]) (implicit G: Monad[G]): G[A]
  66. 66. @kelleyrobinson def runFree[F[_], G[_], A] (f: Free[F, A]) (transform: FunctorTransformer[F, G]) (implicit G: Monad[G]): G[A] Turn F into G - AKA "Natural Transformation"Input `G` must be a monad so we can flatMap
  67. 67. @kelleyrobinson // or 'NaturalTransformation'
 trait FunctorTransformer[F[_], G[_]] {
 def apply[A](f: F[A]): G[A]
 }
 // Common symbolic operator type ~>[F[_], G[_]] = FunctorTransformer[F, G]
  68. 68. @kelleyrobinson /* Function body */ @annotation.tailrec
 def tailThis(free: Free[F, A]): Free[F, A] = free match {
 case FlatMap(FlatMap(fr, fn1), fn2) => ... case FlatMap(Return(a), fn) => ... case _ => ...
 }
 
 tailThis(f) match {
 case Return(a) => ...
 case Suspend(fa) => ... case FlatMap(Suspend(fa), fn) => ... case _ => ...
 } https://github.com/robinske/monad-examples
  69. 69. @kelleyrobinson tailThis(f) match {
 case Return(a) => ...
 case Suspend(fa) => transform(fa) case FlatMap(Suspend(fa), fn) => monad.flatMap(transform(fa))(a => runFree(fn(a))(transform)) case _ => ...
 } https://github.com/robinske/monad-examples
  70. 70. @kelleyrobinson def runLoop[F[_], G[_], A](...): G[A] = {
 var eval: Free[F, A] = f
 
 while (true) {
 eval match {
 case Return(a) => ...
 case Suspend(fa) => ...
 case FlatMap(Suspend(fa), fn) => ...
 case FlatMap(FlatMap(given, fn1), fn2) => ...
 case FlatMap(Return(s), fn) => ...
 }
 }
 
 throw new AssertionError("Unreachable")
 }
  71. 71. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson Evaluating Applies transformation on `Suspend` Trampolining for stack safety
  72. 72. @kelleyrobinson // or 'NaturalTransformation'
 trait FunctorTransformer[F[_], G[_]] {
 def apply[A](f: F[A]): G[A]
 }
 // Common symbolic operator type ~>[F[_], G[_]] = FunctorTransformer[F, G]
  73. 73. @kelleyrobinson type Id[A] = A case class TestEvaluator(var model: Map[String, Boolean]) extends FunctorTransformer[Todo, Id] {
 
 def apply[A](a: Todo[A]): Id[A] }
  74. 74. @kelleyrobinson a match { case NewTask(task) =>
 model = model + (task.toString -> false)
 task
 case CompleteTask(task) =>
 model = model + (task.toString -> true)
 task
 case GetTasks(default) =>
 model.asInstanceOf[A] }
  75. 75. @kelleyrobinson it("should evaluate todos") {
 val result = runFree(todos)(TestEvaluator(Map.empty))
 val expected: Map[String, Boolean] =
 Map(
 "Go to scala days" -> true,
 "Write a novel" -> false,
 "Meet Tina Fey" -> false
 )
 result shouldBe expected
 }
  76. 76. @kelleyrobinson case object ActionTestEvaluator extends FunctorTransformer[Todo, Id] { 
 var actions: List[Todo[String]] = List.empty
 def apply[A](a: Todo[A]): Id[A] }
  77. 77. @kelleyrobinson a match { case NewTask(task) =>
 actions = actions :+ NewTask(task.toString)
 task
 case CompleteTask(task) =>
 actions = actions :+ CompleteTask(task.toString)
 task
 case GetTasks(default) =>
 actions = actions :+ GetTasks("")
 default }
  78. 78. @kelleyrobinson it("should evaluate todos actions in order") {
 runFree(todos)(ActionTestEvaluator)
 
 val expected: List[Todo[String]] =
 List(
 NewTask("Go to scala days"),
 NewTask("Write a novel"), NewTask("Meet Tina Fey"),
 CompleteTask("Go to scala days"), GetTasks("")
 ) 
 ActionTestEvaluator.actions shouldBe expected
 }
  79. 79. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson Defining multiple interpreters allows you to test side-effecting code without using testing mocks.
  80. 80. @kelleyrobinson // Production Interpreter def apply[A](a: Todo[A]): Option[A] = {
 a match {
 case NewTask(task) =>
 /** * Some if DB write succeeds * None if DB write fails * */
 case CompleteTask(task) => ...
 case GetTasks(default) => ...
 }
 }
  81. 81. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson Justifications • Defer side effects • Multiple interpreters • Stack safety
  82. 82. WHY THE FREE MONAD ISN'T FREE #BlueSkyScala The path to learning is broken @kelleyrobinson Credit: Jessica Kerr
  83. 83. WHY THE FREE MONAD ISN'T FREE Freedom isn't free Reasons to avoid the Free Monad • Boilerplate • Learning curve • Alternatives @kelleyrobinson Credit: Jessica Kerr
  84. 84. WHY THE FREE MONAD ISN’T FREE - Monoids, Functors & Monads - How to be “Free” - Why & Why Not “Free” - Alternatives - Real World Applications $ @kelleyrobinson
  85. 85. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson Know your domain
  86. 86. WHY THE FREE MONAD ISN'T FREE Functional Spectrum Where does your team fall? Java Haskell
  87. 87. WHY THE FREE MONAD ISN'T FREE Functional Spectrum Where does your team fall? Java Haskell
  88. 88. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson Alternatives for maintaining stack safety
  89. 89. @kelleyrobinson final override def map[B, That](f: A => B) (implicit bf: CanBuildFrom[List[A], B, That]): That = {
 if (bf eq List.ReusableCBF) {
 if (this eq Nil) Nil.asInstanceOf[That] else {
 val h = new ::[B](f(head), Nil)
 var t: ::[B] = h
 var rest = tail
 while (rest ne Nil) {
 val nx = new ::(f(rest.head), Nil)
 t.tl = nx
 t = nx
 rest = rest.tail
 }
 h.asInstanceOf[That]
 }
 }
 else super.map(f)
 }
  90. 90. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson Alternatives for managing side effects
  91. 91. @kelleyrobinson import java.sql.ResultSet
 
 case class Person(name: String, age: Int)
 
 def getPerson(rs: ResultSet): Person = {
 val name = rs.getString(1)
 val age = rs.getInt(2)
 Person(name, age)
 }
  92. 92. @kelleyrobinson def handleFailure[A](f: => A): ActionResult / A = {
 Try(f) match {
 case Success(res) => res.right
 case Failure(e) => InternalServerError(reason = e.getMessage).left
 }
 } 
 handleFailure(getPerson(rs))
  93. 93. WHY THE FREE MONAD ISN’T FREE - Monoids, Functors & Monads - How to be “Free” - Why & Why Not “Free” - Alternatives - Real World Applications $ @kelleyrobinson
  94. 94. WHY THE FREE MONAD ISN'T FREE Scalaz Scalaz is a Scala library for functional programming. http://scalaz.github.io/scalaz/ Cats Lightweight, modular, and extensible library for functional programming. http://typelevel.org/cats/ @kelleyrobinson http://www.slideshare.net/jamesskillsmatter/real-world-scalaz
  95. 95. WHY THE FREE MONAD ISN'T FREE Examples • Doobie • scalaz.concurrent.Task @kelleyrobinson https://github.com/tpolecat/doobie
  96. 96. @kelleyrobinson import scalaz.concurrent.Task def apply(conf: Config, messages: List[SQSMessage]): Unit = {
 val tasks = messages.map(m => Task {
 processSQSMessage(conf, m)
 })
 
 Task.gatherUnordered(tasks).attemptRun match {
 case -/(exp) => error(s"Unable to process message")
 case _ => ()
 }
 }
  97. 97. @kelleyrobinson // yikes object Task {
 implicit val taskInstance: Nondeterminism[Task] with Catchable[Task] with MonadError[({type λ[α,β] = Task[β]})#λ,Throwable] = new Nondeterminism[Task] with Catchable[Task] with MonadError[({type λ[α,β] = Task[β]})#λ,Throwable] { ... } }
  98. 98. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson My experience... ...what happened?
  99. 99. WHY THE FREE MONAD ISN’T FREE - Know your domain - Use clean abstractions - Share knowledge $ @kelleyrobinson
  100. 100. Thank You! @kelleyrobinson hello@krobinson.me
  101. 101. WHY THE FREE MONAD ISN’T FREE @kelleyrobinson Acknowledgements & Resources Special thanks to: • Sharethrough • Rúnar Bjarnason • Rob Norris • Eugene Yokota • Jessica Kerr • David Hoyt • Danielle Sucher • Charles Ruhland Resources for learning more about Free Monads: • http://blog.higher-order.com/assets/trampolines.pdf • http://eed3si9n.com/learning-scalaz/ • https://stackoverflow.com/questions/44965/what-is-a-monad • https://byorgey.wordpress.com/2009/01/12/abstraction-intuition-and-the-monad-tutorial-fallacy/ • http://hseeberger.github.io/blog/2010/11/25/introduction-to-category-theory-in-scala/ • https://en.wikipedia.org/wiki/Free_object • https://softwaremill.com/free-monads/ • https://github.com/davidhoyt/kool-aid/ • https://www.youtube.com/watch?v=T4956GI-6Lw Other links and resources: • https://skillsmatter.com/skillscasts/6483-keynote-scaling-intelligence-moving-ideas-forward • https://stackoverflow.com/questions/7213676/forall-in-scala but that boilerplate

×