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
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
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))
}
implicit class ConfigReader[A,B](val f: A => B) {
def inject(g: Config => A): Reader[Config,B] =
new Reader[Config,B](g andThen f)
}
This file is literate Scala, and can be run using Codedown:
$ curl https://earldouglas.com/posts/exercises/scala-reader.md |
codedown scala |
xargs -0 scala -nc -e
42
42