3. WHY THE FREE MONAD ISN’T FREE
“Let’s just trampoline it and
add the Free Monad”
@kelleyrobinson
4. WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
“Let’s just trampoline it and
add the Free Monad”
5.
6. Why The Free
Monad Isn’t Free
Kelley Robinson
Data & Infrastructure Engineer
Sharethrough
@kelleyrobinson
7. WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real World Applications
$
@kelleyrobinson
8. WHY THE FREE MONAD ISN’T FREE
github.com/robinske/monad-examples
9. WHY THE FREE MONAD ISN’T FREE
https://twitter.com/rickasaurus/status/705134684427128833
10. WHY THE FREE MONAD ISN’T FREE
Monoids
@kelleyrobinson
17. @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


}
18. @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


}
19. WHY THE FREE MONAD ISN’T FREE
Functors
@kelleyrobinson
21. WHY THE FREE MONAD ISN'T FREE
@kelleyrobinson
Properties
Identity: "no-op" value
Composition:
grouping doesn't matter
22. @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

}

}
24. @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"
)
}
26. WHY THE FREE MONAD ISN’T FREE
Monads
@kelleyrobinson
27. "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
40. WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real World Applications
$
@kelleyrobinson
41. WHY THE FREE MONAD ISN'T FREE
The word "free" is
used in the sense of
"unrestricted" rather
than "zero-cost"
$
@kelleyrobinson
42. 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
43. WHY THE FREE MONAD ISN’T FREE
Free Monoids
@kelleyrobinson
45. WHY THE FREE MONAD ISN’T FREE
Free Monoids
• Free from interpretation
• No lost input data when
appending
@kelleyrobinson
image credit: http://celestemorris.com
53. @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]

54. @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]
55. @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]
56. @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))
57. @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
58. @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))

)

)

)

)
59. WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real World Applications
$
@kelleyrobinson
60. WHY THE FREE MONAD ISN'T FREE
What's the point?
• Defer side effects
• Multiple interpreters
• Stack safety
@kelleyrobinson
68. @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
75. @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]
}
76. @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]
}
77. @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

}
79. @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
}
80. @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

}
81. WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Defining multiple interpreters allows
you to test side-effecting code without
using testing mocks.
82. @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) => ...

}

}
83. WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Justifications
• Defer side effects
• Multiple interpreters
• Stack safety
84. WHY THE FREE MONAD ISN'T FREE
#BlueSkyScala
The path to learning is broken
@kelleyrobinson
Credit: Jessica Kerr
85. 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
86. WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real World Applications
$
@kelleyrobinson
87. WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Know your domain
88. WHY THE FREE MONAD ISN'T FREE
Functional Spectrum
Where does your team fall?
Java Haskell
89. WHY THE FREE MONAD ISN'T FREE
Functional Spectrum
Where does your team fall?
Java Haskell
90. WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Alternatives for
maintaining stack
safety
91. @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)

}
92. WHY THE FREE MONAD ISN’T FREE
@kelleyrobinson
Alternatives for
managing side
effects
94. @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))
95. WHY THE FREE MONAD ISN’T FREE
- Monoids, Functors & Monads
- How to be “Free”
- Why & Why Not “Free”
- Alternatives
- Real World Applications
$
@kelleyrobinson
96. 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