ibrhta
Last Updated: February 25, 2016
·
2.328K
· mortimerpa
4f581c1a7bc4d712fb4f4a51139ed1f8

Generalizing the Reader Tooling, part 2

This post is part of a series where I incrementally build up the Reader dependency injection tool, using different advanced concepts of functional programming in scala:

  1. Dependency Injection for Configuring Play Framework Database Connection(s) part 1
  2. Dependency Injection for Configuring Play Framework Database Connection(s) part 2
  3. Curry and Cake without type indigestion -- Covariance, Contravariance and the Reader Monad
  4. Tooling the Reader Monad
  5. Generalizing the Reader Tooling, part 1
  6. Generalizing the Reader Tooling, part 2

In the post about Tooling the Reader Monad, I have proposed a number of methods to simplify the work with the Reader monad for dependency injection. As I have shown in the previous post, there are ways to make these tools more generic. This was a good excuse to explore typeclasses and monads. Please read the previous posts for some introduction.

If you do not know what the Reader monad does or what a typeclass is, please check out at least the following posts:
Curry and Cake without type indigestion -- Covariance, Contravariance and the Reader Monad and Tooling the Reader Monad and maybe google for these concepts. There are very good basic tutorials out there.

A very common use case with the Reader monad is to have a Future embedded in the Reader, or, depending on how you compose your readers, a Future around your reader, something like this:
```scala

val readerF: Reader[Connection, Future[Int]] = …

val readerF: Future[Reader[Connection, Int]] = …

In the previous post, I have shown how to use a typeclass `CanMap` to build a `sequence` function that moves the future inside a Reader, and convert the second case in the first case. While I mention futures, this happens often with collections and with Option. Imagine:

```scala

    def getUserIds(): Reader[Connection, Seq[Int]] = …

    def readUser(id: Int): Reader[Connection, Option[User]] = …

If you try to combine these readers, you will most probably end up with a Seq[Reader[Connection, Option[User]]] which you could convert to a Reader[Connection, Seq[Option[User]]] and then, using flatten to a Reader[Connection, Seq[User]]. This is why I have introduced a typeclass to deal with this pattern in a generic manner. The CanMap typeclass is defined as follows:


trait CanMap[A, B, M[_]] {
  def map(l: M[A])(f: A => B): M[B]
  def flatMap(l: M[A])(f: A => M[B]): M[B]
}

It defines a set of types that can perform a map or a flatMap on the value they wrap. We can for instance have an implementation for Option:


implicit def canmapopt[A, B] = new CanMap[A, B, Option] {
   def map(l: Option[A])(f: A => B): Option[B] = l.map(f)
   def flatMap(l: Option[A])(f: A => Option[B]): Option[B] = l.flatMap(f)
}

For implementations for Traversable and Future, check out the previous post or this gist. (You will probably have noticed that we are more or less dealing with Monads).

It's nice to be able to move wrappers inside a Reader to make it simpler to compose when doing dependency injection, but you quickly end up having to write complex type signatures and calling map and flatMap in chains. For instance, with our previous example, imagine wanting to get a list of names of users:


val users: Reader[Connection, Seq[User]] = getUserIds().flatMap(list => 
    Reader.sequence(list.map(readerUser)).map(_.flatten)
)
val userNamesReader[Connection, Seq[String]] = users.map(list => list.map(user => user.name)) 

The problem is that "Monads do not compose". However, there is a solution, we can just make a shortcut for our embedded class:


case class ReaderSeq[C, R](r: Reader[C, Seq[R]]) {
     def map[B](f: R => B): ReaderSeq[C, B] = ReaderSeq(r.map(list => list.map(f)))
}

In fact, in the early tooling post, I have defined a very similar wrapper for Futures within Readers. Wouldn't it be nice if we could generalize this?

Well, now that we have this CanMap typeclass that generalizes over all types that have a map and a flatMap method, we can write a generic wrapper for any class for which we have a CanMap.


case class ReaderM[-C, A, M[+A]](val read: Reader[C, M[A]])(implicit canMap: CanMap[A, _, M]) {
   def map[B](f: A => B)(implicit canMap: CanMap[A, B, M]): Reader[C, M[B]] = read.map(in => canMap.map(in)(f))

   def flatMap[B, D <: C](f: A => ReaderM[D, B, M])(implicit canMap: CanMap[A, B, M], canMapB: CanMap[B, _, M]): ReaderM[D, B, M] = 
      ReaderM[D, B, M](read.flatMap { in =>
        Reader.reader { (conn: D) =>
          canMap.flatMap(in) { a: A =>
            f(a)(conn)
          }
        }
     })
}

Here, we define a type ReaderM that wraps a Reader containing a type M, we only accept type Ms that have an implicit implementation of CanMap available in the scope. That is, we are telling the compiler that we only accept types that have a map and a flatMap function. Given this, we can make a map and flatMap that will proxy the map to the type within the Reader.

As we have defined implementations of CanMap for Option, Future and any Traversable, we can apply this to any of these cases. Our previous example can thus become:


val userNamesReaderM[Connection, String, Seq] = ReaderM(users).map(user => user.name)

Final Note

You can find the code, with extended tools for the Reader and CanMap in this gist and for the ReaderM in this gist.

If you are already an advanced functional coder, you will have noticed that CanMap is a simplification of a Functor/Monad and that ReaderM is a Monad Transformer. You can find extended implementation of these in the scalaz library, but the implementation provided here is already fully working and used in production code.

Say Thanks
Respond