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 gtripleToString: 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)


The Reader monad for dependency injection

By danielbedo

The Reader monad for dependency injection

Using the Reader monad for dependency injection

  • 7,907
Loading comments...