Advertisement
Guest User

Untitled

a guest
Jan 17th, 2017
96
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.44 KB | None | 0 0
  1. object FreeMonadExplained {
  2.  
  3. import scala.language.higherKinds
  4. import scala.language.implicitConversions
  5.  
  6. sealed trait Interact[A]
  7. case class Ask(prompt: String) extends Interact[String]
  8. case class Tell(message: String) extends Interact[Unit]
  9.  
  10. // No access to the username captured by the Ask
  11. // val prog = List(
  12. // Ask("What's your name?"),
  13. // Tell("Hello, ???")
  14. // )
  15.  
  16. // doesn't compile because Interact isn't a monad
  17. // val prog = for {
  18. // name <- Ask("What's your name?")
  19. // _ <- Tell(s"Hello, $name")
  20. // } yield ()
  21.  
  22. // We need Interact to be a Monad
  23. trait Monad[M[_]] {
  24. def pure[A](a: A): M[A]
  25. def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B]
  26. // need to obey some rules
  27. }
  28.  
  29. // Free monad
  30. sealed trait Free[F[_], A] {
  31. def flatMap[B](f: A => Free[F, B]): Free[F, B] = this match {
  32. case Return(a) => f(a)
  33. case Bind(i, k) => Bind(i, k andThen (_ flatMap f))
  34. }
  35. def map[B](f: A => B): Free[F, B] = flatMap(a => Return(f(a)))
  36.  
  37. // F = compile time language (e.g Interact)
  38. // G = runtime language (e.g. Id)
  39. // this version is not stack safe (but possible to write it in tail recursive way)
  40. def foldMap[G[_]](f: F ~> G)(implicit monad: Monad[G]): G[A] = this match {
  41. case Return(a) => monad.pure(a)
  42. case Bind(i, k) =>
  43. monad.flatMap(f(i)) { a =>
  44. k(a).foldMap(f)
  45. }
  46. }
  47.  
  48. }
  49. case class Return[F[_], A](a: A) extends Free[F, A] // same as pure
  50. case class Bind[F[_], I, A](i: F[I], k: I => Free[F, A]) extends Free[F, A] // same as flatMap
  51.  
  52. // Interact will be F and we can generate a monad of Free[Interact[_], A]
  53. implicit def liftIntoFree[F[_], A](fa: F[A]): Free[F, A] = Bind[F, A, A](fa, (a: A) => Return(a))
  54.  
  55. // with lift we can write our program
  56. val prog: Free[Interact, Unit] = for {
  57. name <- Ask("What's your name?")
  58. _ <- Tell(s"Hello, $name")
  59. } yield ()
  60.  
  61. // Is it really stacksafe ? Not with this implementation of flatMap
  62. val expandedProg: Free[Interact, Unit] =
  63. Ask("What's your name?").flatMap(name => Tell(s"Hello, $name").map[Unit](_ => Unit))
  64.  
  65. val expandedProg2: Free[Interact, Unit] =
  66. Bind[Interact, String, Unit](
  67. Ask("What's your name?"),
  68. name =>
  69. Bind[Interact, Unit, Unit](
  70. Tell(s"Hello, $name"),
  71. _ => Return(Unit)
  72. )
  73. )
  74.  
  75. // we need a way to convert from F to G so that our Free monad can be turned into another monad
  76. sealed trait ~>[F[_], G[_]] { self =>
  77. def apply[A](f: F[A]): G[A]
  78.  
  79. // the 'or' method allows to compose the transformers
  80. // if we have a transformer that turn F into G
  81. // and another transformer that turn H into G
  82. // we can have a transformer that can turn F or H into G
  83. // That's neat because we can write our interpreter independently of each other
  84. // and combine them together to run our program
  85. def or[H[_]](h: H ~> G) = new (({ type T[x] = CoProduct[F, H, x] })#T ~> G) {
  86. def apply[A](c: CoProduct[F, H, A]): G[A] =
  87. c.value match {
  88. case Left(fa) => self.apply(fa)
  89. case Right(ha) => h(ha)
  90. }
  91. }
  92.  
  93. }
  94.  
  95. type Id[A] = A
  96.  
  97. // run the program using the console interpreter
  98. object Console extends (Interact ~> Id) {
  99. def apply[A](i: Interact[A]) = i match {
  100. case Ask(prompt) =>
  101. println(prompt)
  102. scala.io.StdIn.readLine()
  103. case Tell(message) =>
  104. println(message)
  105. }
  106. }
  107.  
  108. type Tester[A] = Map[String, String] => (List[String], A)
  109.  
  110. // run the program as a test
  111. // the map is our input (prompt -> user input)
  112. // List[String] is what printed to the user
  113. object Test extends (Interact ~> Tester) {
  114. def apply[A](i: Interact[A]) = i match {
  115. case Ask(prompt) =>
  116. inputs =>
  117. (List(), inputs(prompt))
  118. case Tell(message) =>
  119. _ =>
  120. (List(message), ())
  121. }
  122. }
  123.  
  124. // we need to prove that Tester is a monad (to provide the implicit param for foldMap)
  125. // sort of combination between a Reader and a Writer monad
  126. implicit val testerMonad = new Monad[Tester] {
  127. def pure[A](a: A): Tester[A] = _ => (List(), a)
  128. def flatMap[A, B](t: Tester[A])(f: A => Tester[B]): Tester[B] =
  129. inputs => {
  130. val (out1, a) = t(inputs)
  131. val (out2, b) = f(a)(inputs)
  132. (out1 ++ out2, b)
  133. }
  134. }
  135.  
  136. implicit val idMonad = new Monad[Id] {
  137. def pure[A](a: A): Id[A] = a
  138. def flatMap[A, B](a: Id[A])(f: A => Id[B]): Id[B] = f(a)
  139. }
  140.  
  141. // Execute the program on the console
  142. prog.foldMap(Console)
  143. // Execute the program using the given inputs for testing
  144. prog.foldMap(Test).apply(Map("What's your name?" -> "Kilroy"))
  145.  
  146. // let's add another feature: Authorisation
  147. // that's a new concern so instead of extending Interact
  148. // we create an Auth algebra
  149. case class UserId(value: String)
  150. case class Password(value: String)
  151. case class User(userId: UserId)
  152. case class Permission(name: String)
  153.  
  154. sealed trait Auth[A]
  155. case class Login(userId: UserId, password: Password) extends Auth[Option[User]]
  156. case class HasPermission(user: User, permission: Permission) extends Auth[Boolean]
  157.  
  158. object AuthOnlyJohn extends (Auth ~> Id) {
  159. override def apply[A](auth: Auth[A]): Id[A] = auth match {
  160. case Login(UserId("John"), _) => Some(User(UserId("John"))) // don't care what the password is
  161. case _: Login => None
  162. case HasPermission(user, permission) => permission.name == "share_secret" && user.userId.value == "John"
  163. }
  164. }
  165.  
  166. // doesn't compile
  167. // we need a type ??? that can be Either an Interact or an Auth
  168. // val prog: Free[???, Unit] = for {
  169. // userId <- Ask("What's your login?")
  170. // password <- Ask("What's your password?")
  171. // user <- Login(UserId(userId), Password(password))
  172. // hasAccess <- HasPermission(user, Permission("secret"))
  173. // _ <- if (hasAccess) Tell("The secret is BLABLABLA")
  174. // else Tell("Sorry, I can't tell you anything")
  175. // } yield ()
  176.  
  177. // let's create a type that can be either G[A] or F[A]
  178. case class CoProduct[F[_], G[_], A](value: Either[F[A], G[A]])
  179.  
  180. type Appli[A] = CoProduct[Interact, Auth, A]
  181.  
  182. // val prog2: Free[Appli, Unit] = ...
  183.  
  184. // In order to avoid navigating in nested left/right (because of the underlying Either)
  185. // we need to make our types (Interact or Auth) "appear as the same type" (CoProduct)
  186. // we inject them into the CoProduct
  187. sealed trait Inject[F[_], G[_]] {
  188. def inject[A](f: F[A]): G[A]
  189. }
  190. object Inject {
  191. // lift F into the co-product of F and F
  192. implicit def reflexive[F[_]]: Inject[F, F] = new Inject[F, F] {
  193. def inject[A](f: F[A]): F[A] = f
  194. }
  195. // lift F into G where G is the co-product of F and something else
  196. implicit def left[F[_], G[_]]: Inject[F, ({ type T[x] = CoProduct[F, G, x] })#T] =
  197. new Inject[F, ({ type T[x] = CoProduct[F, G, x] })#T] {
  198. def inject[A](f: F[A]): CoProduct[F, G, A] = CoProduct(Left(f))
  199. }
  200. // lift G into F where F is the co-product of G and something else
  201. implicit def right[F[_], G[_], H[_]](implicit i: Inject[F, G]): Inject[F, ({ type T[x] = CoProduct[H, G, x] })#T] =
  202. new Inject[F, ({ type T[x] = CoProduct[H, G, x] })#T] {
  203. // i.inject(f) is a G
  204. def inject[A](f: F[A]): CoProduct[H, G, A] = CoProduct(Right(i.inject(f)))
  205. }
  206. }
  207.  
  208. // now that we have inject we can create a lift that turns an F (e.g. Interact) into a larger type G (e.g. Appli)
  209. def lift[F[_], G[_], A](f: F[A])(implicit i: Inject[F, G]): Free[G, A] =
  210. Bind(i.inject(f), (a: A) => Return(a))
  211.  
  212. // smart constructor that lift an Interact into a CoProduct[Interact, ?]
  213. class Interacts[F[_]](implicit i: Inject[Interact, F]) {
  214. def tell(message: String): Free[F, Unit] = lift(Tell(message))
  215. def ask(prompt: String): Free[F, String] = lift(Ask(prompt))
  216. }
  217.  
  218. // smart constructor that lift an Auth into a CoProduct[Auth, ?]
  219. class Auths[F[_]](implicit i: Inject[Auth, F]) {
  220. def login(userId: UserId, password: Password): Free[F, Option[User]] = lift(Login(userId, password))
  221. def hasPermission(user: User, permission: Permission): Free[F, Boolean] = lift(HasPermission(user, permission))
  222. }
  223.  
  224. // we can finally write our program
  225. def program[F[_]](implicit interacts: Interacts[F], auths: Auths[F]) = {
  226. import interacts._
  227. import auths._
  228. val shareSecret = Permission("share_secret")
  229. for {
  230. userId <- ask("What's your login?")
  231. password <- ask("What's your password?")
  232. user <- login(UserId(userId), Password(password))
  233. hasAccess <- user.map(hasPermission(_, shareSecret)).getOrElse(Return(false))
  234. _ <- if (hasAccess) tell("The secret is BLBALBAL")
  235. else tell("Can't tell you anything")
  236. } yield ()
  237. }
  238.  
  239. // huge achievement but how do we run it ?
  240. // we need a co-product interpreter (see above)
  241.  
  242. // now we can proceed
  243. implicit val interacts = new Interacts[Appli]
  244. implicit val auths = new Auths[Appli]
  245. val app: Free[Appli, Unit] = program[Appli]
  246. def runApp() = app.foldMap(Console or AuthOnlyJohn)
  247.  
  248. }
  249.  
  250. // to define a library based on Free
  251. // - define your algebra data types (sealed trait and case classes)
  252. // - make smart constructors to lift them into coproduct
  253. // - define individual interpreters
  254.  
  255. // to use a library defined above
  256. // - write programs using smart constructor
  257. // - compose the appropriate interpreters
  258. // - fold the program using the interpreter
  259.  
  260. // if G is the Free monad it gives stratified application
  261. // def foldMap[G[_]](f: F ~> G)(implicit monad: Monad[G]): G[A]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement