the reader monad
- function composition in scala
- dependency injection with the reader monad
- using the reader alongside the cake pattern
- possible problems and solutions
composing functions
the only language construct we need
scala> val f = (i: Int) => i*3
f: Int => Int = <function1>
f: Int => Int is just a fancy way of saying Function1[Int, Int]
def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) }
scala> val f = (i: Int) => i*3 f: Int => Int = <function1> scala> val g = (i: Int) => i.toString g: Int => String = <function1>
scala> val tripleToString = f andThen g
tripleToString: Int => String = <function1>
scala> tripleToString(2) res0: String = 6
monads
A type with a monad structure defines what it means to chain operations, or nest functions of that type together.
THE READER MONAD
- a monad for unary operations
- using andThen as a map operation
- in the end its just a Function1
- scalaz.Reader provides map and flatMap operations
scalaz.reader
scala> import scalaz.Reader
import scalaz.Reader
scala> val f = Reader((i: Int) => i*3)
f: scalaz.Reader[Int,Int] = scalaz.KleisliFunctions$$anon$17@53628ee
new readers can be created with map and flatMap
val g = f map (i => i + 2)
val h = for (i <- g) yield i.toString
dependency injection with the reader monad
define functions requiring a dependency as a Reader with the dependency
let's see an example
trait Users {
def getUser(id: Int) = Reader((userRepository: UserRepository) =>
userRepository.get(id)
)
def findUser(username: String) = Reader((userRepository: UserRepository) =>
userRepository.find(username)
)
}
getUser returns a Reader[UserRepository, User] not a User!
what does that mean?
I'm gonna return a User, when I get a UserRepository
The actual injection is deferred
map, flatmap all the monadic goodness
object UserInfo extends Users {
def userEmail(id: Int) = { getUser(id) map (_.email) } def userInfo(username: String) = for { user <- findUser(username) boss <- getUser(user.supervisorId) } yield Map( "fullName" -> s"${user.firstName} ${user.lastName}", "email" -> s"${user.email}", "boss" -> s"${boss.firstName} ${boss.lastName}" )
}
userEmail returns Reader[UserRepository, String]
but we don't have to mention the Repository anywhere except our primitive Readers
multiple dependencies?
trait Repositories {
def userRepository: UserRepository
def questionRepository: QuestionRepository
}
Now we just have to change our primitive readers to accept the Repositories instead of a single one
trait Users {
def getUser(id: Int) = Reader((repos: Repositories) =>
repos.userRepository.get(id)
)
}
getUser is now a Reader[Repositories, User]
Injecting the dependency
At an outer level of our application we actually need to inject the dependencies
object Application extends Application(UserRepositoryImpl)
class Application(userRepository: UserRepository) with Users {
def getUserEmail(id: Int) = Action {
HttpOk(UserInfo.userEmail(id)(userRepository))
}
}
why not use it with cake together?
object Application extends Application with UserRepositoryComponentImpl
trait Application extends Controller with Users {
this: UserRepositoryComponent =>
def getUserEmail(id: Int) = Action {
HttpOk(UserInfo.userEmail(id)(userRepository))
}
}
-
Cake pattern at the outer edge
- Reader monad in the core
- Reader monad lets us push the dependency injection from the core to the outer levels too
Problems
what about futures?
- Two monads do not combine automatically
- Future[Reader[A,B]], Future[Reader[A, Future[B]]] ?!
- Better to have reader of futures that we can combine
- Monad transformers in scalaz or simple implicit conversions
additional readings and inspiration
Tooling the reader monad (working with Futures)
(writer of functional programmign in scala)
The Reader monad for dependency injection
By danielbedo
The Reader monad for dependency injection
Using the Reader monad for dependency injection
- 9,907