– the reader monad



– the reader monad

0 0


readermonad

my talk at GuteFrage about using the reader monad for dependency injection

On Github danielbedo / readermonad

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]

<span style="font-family: monospace;"> def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) }</span><br>
scala> val f = (i: Int) => i*3
f: Int => Int = <function1>

scala> val g = (i: Int) => i.toString
g: Int => String = <function1>scala<span>> </span><span style="font-family: monospace;">val tripleToString = f andThen g</span>tripleToString: Int => String = <function1><br>scala> tripleToString(2)
res0: String = 6<br>

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

<font style="font-size: 18px;">trait Users {
  def getUser(id: Int) = Reader((userRepository: UserRepository) =>
    userRepository.get(id)
  )

  def findUser(username: String) = Reader((userRepository: UserRepository) =>
    userRepository.find(username)
  )
}</font>
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 {<br>  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)