We've updated our privacy policy. Click here to review the details. Tap here to review the details.

Successfully reported this slideshow.

Your SlideShare is downloading.
×

Activate your 30 day free trial to unlock unlimited reading.

Activate your 30 day free trial to continue reading.

Top clipped slide

1 of 125
Ad

Download to read offline

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

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

- 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.
- 3. Decision/Interpretation (tangled) def updateAccount(user: User, db: KVStore): Unit = { val account = db.get(user.id) if (!account.suspended) db.put(user.id, account.updateSpecialOffers) else if (account.abandoned) db.delete(user.id) }
- 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
- 5. Decision def chooseAction(user: User, account: Account): KVSAction = { if (!account.suspended) Put(user.id, account.updateSpecialOffers) else if (account.abandoned) Delete(user.id) else NoAction }
- 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]
- 14. Why “free monads”?
- 15. Why “free monads”?
- 16. Why “free monads”?
- 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
- 18. Remedial interlude
- 19. Functors • Functors are things you can map over • F[A] => (A => B) => F[B] trait F[A] { def map(f: A => B): F[B] }
- 20. Functors trait F[A] { def map(f: A => B): F[B] }
- 21. Functors trait F[A] { def map(f: A => B): F[B] }
- 22. Functors trait F[A] { def map(f: A => B): F[B] }
- 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)
- 26. Back to our regularly scheduled program…
- 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
- 29. Free Blargles from Fraxblatts • A Fraxblatt is said to generate a Free Blargle if: 1. NO JUNK 2. NO NOISE
- 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)) } }
- 39. Stepping through flatMap Let’s plug in a really simple functor and see what happens. case class Box[A](a: A)
- 40. Stepping through flatMap Let’s plug in a really simple functor and see what happens. case class Box[A](a: A) { def map[B](f: A => B) = Box(f(a)) }
- 41. banana
- 42. Return(banana)
- 43. Box(Return(banana))
- 44. Suspend(Box(Return(banana)))
- 45. that.flatMap(banana => Return(banana.peel))
- 46. that.flatMap(banana => Return(banana.peel))
- 47. that.flatMap(banana => Return(banana.peel))
- 48. that.flatMap(banana => Return(banana.peel))
- 49. liftF Let’s automate creating the Suspend cell! F[A] => Free[F, A] =>
- 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=>
- 89. Trampolines
- 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
- 91. Trampolines • Let’s take a look at some code…
- 92. Now for the power tool
- 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
- 103. 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, ()) )
- 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
- 106. The payoff
- 107. Composable scripts def modify(key: String, f: String => String): Free[KVS, Unit] = for { v <- get(key) _ <- put(key, f(v)) } yield ()
- 108. Harmless imperative code val script: Free[KVS, Unit] = for { id <- get(“swiss-bank-account-id”) _ <- modify(id, (_ + 1000000)) _ <- put(“bermuda-airport”, “getaway car”) _ <- delete(“tax-records”) } yield ()
- 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)
- 120. Tank game
- 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
- 124. Further reading https://github.com/kenbot/free
- 125. Thank you Hope you enjoyed hearing about Free Monads!

No public clipboards found for this slide

You just clipped your first slide!

Clipping is a handy way to collect important slides you want to go back to later. Now customize the name of a clipboard to store your clips.Hate ads?

Enjoy access to millions of presentations, documents, ebooks, audiobooks, magazines, and more **ad-free.**

The SlideShare family just got bigger. Enjoy access to millions of ebooks, audiobooks, magazines, and more from Scribd.

Cancel anytime.Total views

13,042

On SlideShare

0

From Embeds

0

Number of Embeds

299

Unlimited Reading

Learn faster and smarter from top experts

Unlimited Downloading

Download to take your learnings offline and on the go

You also get free access to Scribd!

Instant access to millions of ebooks, audiobooks, magazines, podcasts and more.

Read and listen offline with any device.

Free access to premium services like Tuneln, Mubi and more.

We’ve updated our privacy policy so that we are compliant with changing global privacy regulations and to provide you with insight into the limited ways in which we use your data.

You can read the details below. By accepting, you agree to the updated privacy policy.

Thank you!

We've encountered a problem, please try again.