Teoría de Cate-movidas: Mónada Reader

Después de perder el miedo a las mónadas se abre un mundo de posibilidades. Hoy vamos a ver una mónada en concreto: la mónada Reader. Esta mónada nos permitirá realizar inyección de dependencias en nuestra aplicación. Hace unas semanas vimos como utilizar el Cake Pattern. Si no te gustó demasiado, presta atención 🙂

La mónada Reader está presente en Scalaz, por lo que si queremos utilizarla, debemos importar esta librería en nuestro proyecto.

import scalaz.Reader

El constructor de la mónada Reader recibe una función unitaria, es decir, una función con un solo argumento. Con la mónada podemos trabajar con la función unitaria directamente y de forma transparente, como si fuera una instancia de tipo Function1.

import scalaz.Reader

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

¿Y ya está? Pues no. La gracia de usar la mónada reader es que una vez instanciada podemos utilizar el método map para transformar el resultado de la función:

import scalaz.Reader

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

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

¿Y cómo nos ayuda esto con la inyección de dependencias?

Utilizando esta mónada, podemos inyectar dependencias en un componente, al igual que hacíamos con el Cake Pattern. Vamos a utilizar un ejemplo sencillo. Aprovechando que se trata de la mónada Reader, vamos a utilizar un ejemplo basado en una biblioteca.

6400b4d28d122bfa1876eda6a27d169e

En la biblioteca será necesario inyectar un repositorio. La implementación genérica será la siguiente:

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

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

Para inyectar la dependencia, se utilizará como parámetro de entrada de la función unitaria el repositorio de forma genérica.

trait Library {
  import scalaz.Reader

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

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

Más adelante, vamos a querer conocer determinada información de los libros, por ejemplo, el nombre. Para ello se utilizará la función map, que nos permitirá cambiar el resultado de la consulta.

object LibraryInfo extends Library {

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

}

Finalmente, vamos a utilizar esta información por ejemplo en una API REST:

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{})

Lo más importante es el método privado run. Este método es el encargado de aplicar el repositorio implementado en cada una de las mónadas readers que hemos definido en nuestro programa.

Además, utilizamos un argumento en nuestra aplicación para definir la implementación del repositorio que queremos inyectar. De esta manera, podemos inyectar un repositorio de prueba cuando tengamos que hacer tests, facilitando en gran medida la construcción de los mismos.

¡Esto es todo amigos!

Anuncios

2 thoughts on “Teoría de Cate-movidas: Mónada Reader

  1. Hola!

    Como siempre gracias por los posts 😀

    para la funcion run hay algo que no entiendo,

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

    por que se define el reader de tipo Reader[Repository, A]?

    en getAllBooks veo que lo que devuelve es un Reader que recibe/usa el repositorioRepositoryImpl que se creó

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

    object UniversityApp extends UniversityApp(new RepositoryImpl{})

    Pero me chirría el segundo tipo A… qué me estoy perdiendo?

    Me gusta

    • Gracias por el apoyo al blog 🙂

      La mónada Reader está parametrizada mediante dos parámetros distintos. Lo puedes ver como una función de un solo argumento. Por tanto: Reader[A, B] será una función de tipo A -> B, donde, dado un A podremos ser capaces de obtener algo de tipo B.

      Si por ejemplo definimos una mónada Reader que dado un Repositorio nos devuelva un libro, el tipo de la mónada será Reader[Repository, Book]. Este sería el caso del método getBook. Por otro lado, el método getAllBooks será una mónada Reader de tipo Reader[Repository, List[Book]].

      Debido a que queremos crear un método run que pueda ser utilizado con cualquiera de las dos mónadas, definimos que se utilizará para mónadas Reader de tipo [Repository, A], y que el tipo de salida se infiera directamente en función de si utilizamos getBook o getAllBooks.

      Gracias por tu feedback 😉

      Un saludo

      Me gusta

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