Abstract alge… what? The Reader monad

By losing our fear of monads, a whole new world of possibilities has opened up. Today we’ll take a look at one specific monad: the Reader monad. This monad will allow us to perform the dependency injection in our application.A few weeks ago we saw how to use Cake Pattern. If you didn’t like that that much, pay attention 🙂

The monad Reader belongs to Scalaz and thus, if we want to use it, we have to import this library in our project.

import scalaz.Reader

The constructor of the Reader monad gets a unary function as input, that is, a function with just one argument. With the monad, we’ll be able to work with the unary function in a direct and transparent way, as if it were an instance of the Function1 type.

import scalaz.Reader

val plus100 = Reader((n: Int) => n + 100)
plus100(1) //101

And that’s all? Well, no. The good thing about using the Reader monad is that once it is instantiated, we can use the map method to transform the result of the function:

import scalaz.Reader

val plus100 = Reader((n: Int) => n + 100)
plus100(1) //101

val doublePlus100 = plus100.map(_ * 2)
doublePlus100(1) //202

And how can this be helpful with dependency injection?

By using this monad, we’ll be able to inject dependencies in a component, same thing that we could do with Cake Pattern. Let’s look at a simple example. Since we’re talking about the Reader monad, let’s use an example based on a library.

6400b4d28d122bfa1876eda6a27d169e

It is required that our library has a repository injected to it. The generic implementation will be the following one:

case class Book(isbn: String, name: String)

trait Repository {
  def get(isbn: String): Book
  def getAll: List[Book]
}

In order to inject the dependency, the repository will be used as input argument to the unary function in a generic way.

trait Library {
  import scalaz.Reader

  def getBook(isbn: String) =
    Reader(
      (repository: Repository) => repository.get(isbn)
    )

  def getAllBooks =
    Reader(
      (repository: Repository) => repository.getAll
    )
}

Further on, we may want to know some information about the books, for instance, the title. For that, map function will be used, which will allow us to modify the result of the query.

object LibraryInfo extends Library {

  def bookName(isbn: String) =
    getBook(isbn) map (_.name)

}

Eventually, we will use that information, for example, in a REST API:

import scalaz.Reader

class UniversityApp(repository: Repository) extends Library {
  
  //GET ~/books/{id}/name
  def getBookName(isbn: String) =
    run(LibraryInfo.bookName(isbn))

  //GET ~/books/
  def getAll = run(getAllBooks)

  private def run[A](reader: Reader[Repository, A]): String = {
    reader(repository).toString
  }
}

object UniversityApp extends UniversityApp(new RepositoryImpl{})

The most important thing here is the private method run. This method is the one in charge of applying the implemented repository to every Reader monad that we have defined in our program.

Besides, we use an argument in our application to define the implementation of the repository we want to inject. This way, we can inject a test repository when tests need to be performed, facilitating to a great extent the construction of them.

That’s all folks!

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s