Free Monads are a powerful technique that can separate the representation of programs from the messy details of how they get run.
I'll go into the details of how they work, how to use them for fun and profit in your own code, and demonstrate a live Free Monad-driven tank game.
Supporting code at https://github.com/kenbot/free
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Running Free with the Monads
1. Run free with
the monads!
Free Monads for fun and profit
@KenScambler
#scalamelb March 2014
2. The problem
• Separation of concerns is paramount to software
• In FP, we try to banish effects to the peripheries of
our programs
• Results and decisions must be represented as
data, such as ADTs
• Interpretation can happen later
• Not super expressive though.
4. Decisions as data
sealed trait KVSAction
case class Put(key: String,
value: String) extends KVSAction
case class Delete(key: String) extends KVSAction
case object NoAction extends KVSAction
6. Interpretation
def interpret(action: KVSAction): Unit = {
action match {
case Put(key, value) => db.put(key, value)
case Delete(key) => db.delete(key)
case NoAction => ()
}
}
val account = db.get(bob,id)
interpret(chooseAction(bob, account))
7. How far can we push it?
• Can our pure “decision” data be as sophisticated
as a program?
• Can we create DSLs that can be run later in
different ways?
• Can we manipulate & rewrite our “program” on the
fly?
• Conditional logic?
• Loops?
• Coroutines?
8. How far can we push it?
def updateAccount(user: User): Unit =
for {
account <- getAccount(user.id)
_ <- when(!account.suspended)(
put(user.id, user.updated))
_ <- when(account.abandoned)(
delete(user.id))
} yield ()
9. The class called “Free”
• Free is a data structure
• Tree of computations
Free[F[_], A]
10. The class called “Free”
• Free is a data structure
• Tree of computations
Free[F[_], A]
11. The class called “Free”
Suspend(F[Free[F,A]])
Return(A)
Free[F[_], A]
12. The class called “Free”
Suspend(F[Free[F,A]])
Return(A)
Free[F[_], A]
13. The class called “Free”
Suspend(F[Free[F,A]])
Return(A)
Free[F[_], A]
17. Why “free monads”?
If F[_] is a functor, Free is a
monad…… for free!
• This buys us a whole world of existing functionality
• Better abstraction
• Sequential computations
• Elegant imperative-style syntax
23. Monads
• Monads have a flatMap method that allows you to
chain computations together sequentially
class M[A] {
def map(f: A => B): M[B]
def flatMap(f: A => M[B]): M[B]
}
24. Monads
• Nesting flatmaps allows sequential actions, ignoring
the specific context!
nbaTeams.flatMap { team =>
team.players.flatMap { player =>
player.gamesPlayed.map { game =>
BasketballCard(team, player, game)
}
}
}
25. Monads
• Neat comprehension syntax in Scala and Haskell
• Makes it look like a regular program
for {
team <- nbaTeams
player <- team.players
game <- player.gamesPlayed
}
yield BasketballCard(team, player, game)
27. “Free objects” in maths
• Important concept in maths!
• Many free structures in Category Theory
• Free Monoids, Free Monads, Free Categories, Free
Groups, etc
• It only counts as “free” if the free thing gets
generated in the simplest possible way
28. Free Blargles from
Fraxblatts
• A Fraxblatt is said to generate a Free Blargle if:
1. The Blargle doesn’t contain anything not directly
produced from a Fraxblatt
2. The Blargle doesn’t contain anything beyond what
it needs to be a Blargle
30. Making an honest monad
of it
case class Return[F[_], A](a: A) extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = ???
}
• Define flatMap for Return:
31. Making an honest monad
of it
case class Return[F[_], A](a: A) extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = f(a)
}
32. Making an honest monad
of it
• Define flatMap for Suspend:
case class Suspend[F[_], A](next: F[Free[F,A]])
extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = ???
}
33. Making an honest monad
of it
• We need to map over the functor
case class Suspend[F[_], A](next: F[Free[F,A]])
extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = {
F??? map ???
}
}
F[???]
34. Making an honest monad
of it
• “next” is the only F we have lying around
case class Suspend[F[_], A](next: F[Free[F,A]])
extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = {
next map {free => ???}
}
}
F[Free[F, ???]]
35. Making an honest monad
of it
• flatMap is almost the only thing we can do to a Free
case class Suspend[F[_], A](next: F[Free[F,A]])
extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = {
next map {free => free.flatMap(???)}
}
}
F[Free[F, ???]]
36. Making an honest monad
of it
• Mapping function f will turn our As into Free[F, B]s
case class Suspend[F[_], A](next: F[Free[F,A]])
extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = {
next map {free => free.flatMap(f)}
}
}
F[Free[F, B]]
37. Making an honest monad
of it
• Wrapping in Suspend matches the type signature!
case class Suspend[F[_], A](next: F[Free[F,A]])
extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = {
Suspend(next map {free => free.flatMap(f)})
}
}
Free[F, B]
38. Making an honest monad
of it
• Cleaning up the syntax a bit…
case class Suspend[F[_], A](next: F[Free[F,A]])
extends Free[F, A] {
def flatMap(f: A => Free[F, B]): Free[F, B] = {
Suspend(next map (_ flatMap f))
}
}
50. More flatmapping
for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
51. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
1
52. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
1
53. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
1
54. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
1
55. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
1
56. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
2
57. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
2
58. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
2
59. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
2
60. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
3
61. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
3
62. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
3
63. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
3
64. for {
a <- liftF( Box(1) )
b <- liftF( Box(2) )
c <- liftF( Box(3) )
} yield a + b + c
6
65. Free[Box, A]
• Chain of nothings, resulting in a single value
• Not very useful!
66. Free[List, A]
for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
67. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2 3
68. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2 3
69. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2 3
70. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2 3
71. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2 3
72. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2
2 4
3 6
73. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2
2 4
3 6
74. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2
2 4
3 6
75. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
1 2
2 4
3 6
76. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
77. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
78. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
79. for {
a <- liftF( List(1,2,3) )
b <- liftF( List(a,a*2) )
c <- liftF( Nil )
} yield a + b + c
80. Free[List,A]
• Branching tree shape, with data at the leaves
• Empty lists can terminate the tree, not just Return.
• Again, not super useful.
The functor controls the branching
factor!
81. Funky functions
• Functors are not just data structures that hold values
• They are computations!
• Free’s real power is unleashed when the Functor
maps over functions!
82. Free[Function0, A]
• No-arg functions, basically a lazy value
• Flatmapping the free composes functions
• Doesn’t actually run any code
83. Free[Function0, A]
for {
a <- liftF(() => 2 + 3)
b <- liftF(() => a * 2)
c <- liftF(() => a * b)
} yield a + b + c
84. for {
a <- liftF(() => 2 + 3)
b <- liftF(() => a * 2)
c <- liftF(() => a * b)
} yield a + b + c
=> 2 + 3
85. for {
a <- liftF(() => 2 + 3)
b <- liftF(() => a * 2)
c <- liftF(() => a * b)
} yield a + b + c
2 + 3=>
86. for {
a <- liftF(() => 2 + 3)
b <- liftF(() => a * 2)
c <- liftF(() => a * b)
} yield a + b + c
2 + 3=>
87. for {
a <- liftF(() => 2 + 3)
b <- liftF(() => a * 2)
c <- liftF(() => a * b)
} yield a + b + c
=>
2 + 3
88. for {
a <- liftF(() => 2 + 3)
b <- liftF(() => a * 2)
c <- liftF(() => a * b)
} yield a + b + c
=> => 2 + 3=>
90. Trampolines
• Believe it or not, Free[Function0,A] is incredibly
useful!
• Also known as Trampoline[A]
• Moves tail calls onto the heap, avoiding stack
overflows
• The best we can get for mutual tail recursion on the
JVM
93. Little languages
• Small, imperative DSLs
• Don’t directly do anything, can be interpreted
many ways
• Functionally pure and type-safe
94. A key-value store DSL
• A bit like the KVSAction ADT way back at the start
• There’s a “type hole” for the next thing
• That means…. we can make it a Functor!
• Mechanical translation from corresponding API
functions
95. A key-value store DSL
sealed trait KVS[Next]
case class Put[Next](key: String,
value: String,
next: Next) extends KVS[Next]
case class Delete[Next](key: String,
next: Next) extends KVS[Next]
case class Get[Next](key: String,
onValue: String => Next) extends KVS[Next]
96. A key-value store DSL
sealed trait KVS[Next]
case class Put[Next](key: String,
value: String,
next: Next) extends KVS[Next]
case class Delete[Next](key: String,
next: Next) extends KVS[Next]
case class Get[Next](key: String,
onValue: String => Next) extends KVS[Next]
Just have a slot for the
next thing, if we don’t
care about a result
value
97. A key-value store DSL
sealed trait KVS[Next]
case class Put[Next](key: String,
value: String,
next: Next) extends KVS[Next]
case class Delete[Next](key: String,
next: Next) extends KVS[Next]
case class Get[Next](key: String,
onValue: String => Next) extends KVS[Next]
Have a Result => Next
function, if we want to
“return” some Result.
98. Which looks a bit like…
def put[A](key: String, value: String): Unit
def delete[A](key: String): Unit
def get[A](key: String): String
99. Which is a bit like…
def put[A](key: String, value: String): Unit
def delete[A](key: String): Unit
def get[A](key: String): String
100. A functor for our KVS
new Functor[KVS] {
def map[A,B](kvs: KVS[A])(f: A => B): KVS[B] =
kvs match {
case Put(key, value, next) =>
Put(key, value, f(next))
case Delete(key, next) =>
Delete(key, f(next))
case Get(key, onResult) =>
Get(key, onResult andThen f)
}
}
101. A functor for our KVS
new Functor[KVS] {
def map[A,B](kvs: KVS[A])(f: A => B): KVS[B] =
kvs match {
case Put(key, value, next) =>
Put(key, value, f(next))
case Delete(key, next) =>
Delete(key, f(next))
case Get(key, onResult) =>
Get(key, onResult andThen f)
}
}
To map over the next
value, just apply f
102. A functor for our KVS
new Functor[KVS] {
def map[A,B](kvs: KVS[A])(f: A => B): KVS[B] =
kvs match {
case Put(key, value, next) =>
Put(key, value, f(next))
case Delete(key, next) =>
Delete(key, f(next))
case Get(key, onResult) =>
Get(key, onResult andThen f)
}
}
To map over a function
yielding the next value,
compose f with it
104. Lifting into the Free
Monad
def put(key: String, value: String): Free[KVS, Unit] =
liftF( Put(key, value, ()) )
def get(key: String): Free[KVS, String] =
liftF( Get(key, identity) )
def delete(key: String): Free[KVS, Unit] =
liftF( Delete(key, ()) )
Initialise with Unit,
when we don’t care
about the value
105. Lifting into the Free
Monad
def put(key: String, value: String): Free[KVS, Unit] =
liftF( Put(key, value, ()) )
def get(key: String): Free[KVS, String] =
liftF( Get(key, identity) )
def delete(key: String): Free[KVS, Unit] =
liftF( Delete(key, ()) )
Initialise with the
identity function, when
we want to return a
value
109. Pure interpreters
type KVStore = Map[String, String]
def interpretPure(kvs: Free[KVS, Unit],
table: KVStore): KVStore =
kvs.resume.fold({
case Get(key, onResult) =>
interpretPure(onResult(table(key)), table)
case Put(key, value, next) =>
interpretPure(next, table + (key -> value))
case Delete(key, next) =>
interpretPure(next, table - key)
}, _ => table)
110. Pure interpreters
type KVStore = Map[String, String]
def interpretPure(kvs: Free[KVS, Unit],
table: KVStore): KVStore =
kvs.resume.fold({
case Get(key, onResult) =>
interpretPure(onResult(table(key)), table)
case Put(key, value, next) =>
interpretPure(next, table + (key -> value))
case Delete(key, next) =>
interpretPure(next, table - key)
}, _ => table)
KVStore is immutable
111. Pure interpreters
type KVStore = Map[String, String]
def interpretPure(kvs: Free[KVS, Unit],
table: KVStore): KVStore =
kvs.resume.fold({
case Get(key, onResult) =>
interpretPure(onResult(table(key)), table)
case Put(key, value, next) =>
interpretPure(next, table + (key -> value))
case Delete(key, next) =>
interpretPure(next, table - key)
}, _ => table)
F[Free[F, A]] / A
Resume and fold…
112. Pure interpreters
type KVStore = Map[String, String]
def interpretPure(kvs: Free[KVS, Unit],
table: KVStore): KVStore =
kvs.resume.fold({
case Get(key, onResult) =>
interpretPure(onResult(table(key)), table)
case Put(key, value, next) =>
interpretPure(next, table + (key -> value))
case Delete(key, next) =>
interpretPure(next, table - key)
}, _ => table)
KVS[Free[KVS, Unit]] / Unit
Resume and fold…
113. Pure interpreters
type KVStore = Map[String, String]
def interpretPure(kvs: Free[KVS, Unit],
table: KVStore): KVStore =
kvs.resume.fold({
case Get(key, onResult) =>
interpretPure(onResult(table(key)), table)
case Put(key, value, next) =>
interpretPure(next, table + (key -> value))
case Delete(key, next) =>
interpretPure(next, table - key)
}, _ => table)
When resume finally returns
Unit, return the table
114. Pure interpreters
type KVStore = Map[String, String]
def interpretPure(kvs: Free[KVS, Unit],
table: KVStore): KVStore =
kvs.resume.fold({
case Get(key, onResult) =>
interpretPure(onResult(table(key)), table)
case Put(key, value, next) =>
interpretPure(next, table + (key -> value))
case Delete(key, next) =>
interpretPure(next, table - key)
}, _ => table)
115. Effectful interpreter(s)
type KVStore = mutable.Map[String, String]
def interpretImpure(kvs: Free[KVS,Unit],
table: KVStore): Unit =
kvs.go {
case Get(key, onResult) =>
onResult(table(key))
case Put(key, value, next) =>
table += (key -> value)
next
case Delete(key, next) =>
table -= key
next
}
116. Effectful interpreters
type KVStore = mutable.Map[String, String]
def interpretImpure(kvs: Free[KVS,Unit],
table: KVStore): Unit =
kvs.go {
case Get(key, onResult) =>
onResult(table(key))
case Put(key, value, next) =>
table += (key -> value)
next
case Delete(key, next) =>
table -= key
next
}
Mutable map
117. Effectful interpreters
type KVStore = mutable.Map[String, String]
def interpretImpure(kvs: Free[KVS,Unit],
table: KVStore): Unit =
kvs.go {
case Get(key, onResult) =>
onResult(table(key))
case Put(key, value, next) =>
table += (key -> value)
next
case Delete(key, next) =>
table -= key
next
}
def go(f: F[Free[F, A]] => Free[F, A]): A
118. Effectful interpreter(s)
type KVStore = mutable.Map[String, String]
def interpretImpure(kvs: Free[KVS,Unit],
table: KVStore): Unit =
kvs.go {
case Get(key, onResult) =>
onResult(table(key))
case Put(key, value, next) =>
table += (key -> value)
next
case Delete(key, next) =>
table -= key
next
}
119. How-to summary
1. Fantasy API
2. ADT with type hole for next value
3. Functor definition for ADT
4. Lifting functions
5. Write scripts
6. Interpreter(s)
121. Conclusion
• Free Monads are really powerful
• Separate decisions from interpretation, at a more
sophisticated level
• Type-safe
• Easy to use!
122. Conclusion
• Express your decisions in a “little language”
• Pause and resume programs, co-routine style
• Rewrite programs macro-style
• Avoid stack overflows with Trampolines
This is a great tool to have in your toolkit!
123. Further reading
• Awodey, Category Theory
• Bjarnason, Dead Simple Dependency Injection
• Bjarnason, Stackless Scala with Free Monads
• Doel, Many roads to Free Monads
• Ghosh, A Language and its Interpretation: Learning
Free Monads
• Gonzalez, Why Free Monads Matter
• Haskell.org, Control.Monad.Free
• Perrett, Free Monads, Part 1
• Scalaz, scalaz.Free