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 {
<- get("x")
x = timesTwo(x)
a <- get("y")
y = plusTwo(y)
b } 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 {
<- timesTwo.inject(_.x)
a <- plusTwo.inject(_.y)
b } 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