Dependency Injection in Scala

June 8, 2014

Given a function run and a class Reader that wraps it:

case class Reader[E,A](run: E => A) {

  def map[B](g: A => B): Reader[E,B] = ???

  def flatMap[B](g: A => Reader[E,B]): Reader[E,B] = ???
}

And given a couple of functions with dependencies:

val timesTwo: Int => Int = x => x * 2

val plusTwo: Int => Int = x => x + 2

Implement map and flatMap so that we can use Reader in a for-comprehension:

def get(k: String): Reader[Map[String,Int],Int] =
  Reader(m => m(k))

val program: Reader[Map[String,Int],Int] =
  for {
    x <- get("x")
    a  = timesTwo(x)
    y <- get("y")
    b  = plusTwo(y)
  } yield a * b

println(program.run(Map("x" -> 3, "y" -> 5))) // 42

Bonus

Implement inject to automatically lift functions with dependencies into a configuration-specific Reader:

trait Config {
  def x: Int
  def y: Int
}

val program2: Reader[Config,Int] =
  for {
    a <- timesTwo.inject(_.x)
    b <- plusTwo.inject(_.y)
  } yield a * b

println(program2.run(new Config { val x = 3; val y = 5 })) // 42

Solution

Here is one possible solution.
case class Reader[E,A](run: E => A) {

  def map[B](g: A => B): Reader[E,B] =
    Reader(e => g(run(e)))

  def flatMap[B](g: A => Reader[E,B]): Reader[E,B] =
    Reader(e => g(run(e)).run(e))
}

Bonus

implicit class ConfigReader[A,B](val f: A => B) {
  def inject(g: Config => A): Reader[Config,B] =
    new Reader[Config,B](g andThen f)
}

Demo

This file is literate Scala, and can be run using Codedown:

$ curl https://earldouglas.com/exercises/scala-reader.md |
  codedown scala |
  xargs -0 scala -nc -e
42
42