Replace Pass-Through Arguments with the Reader Monad


You have multiple functions that reuse an argument, either directly or to pass along to another function call.

Remove the argument from functions that don't need it, and remodel functions that do need it as partially-curried on that argument.


When dependencies are injected at the top level, they have to be passed around from one function to another, regardless of whether or not any one particular function actually uses them.

The reader monad lets us encode dependencies directly into the type, while providing composability.


We use the following form for our reader monad:

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

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

  def flatMap[E2 <: E, B](f: A => Reader[E2, B]): Reader[E2, B] =
    Reader(e => f(run(e)).run(e))

When encountering functions that pass an argument around to each other, extract the common dependency, convert the blocks that need it to readers of that dependency, and put them back together using map and flatMap.


val PI = 3.14

def area(pi: Double, r: Int): Double = pi * r * r
def volume(pi: Double, r: Int, h: Int): Double =
  val a = area(pi, r)
  val v = a * h
val vol = volume(PI, 6, 7)
println(s"vol: ${vol}") // vol: 791.28

With a reader, we can extract Pi as a dependency:

def areaR(r: Int): Reader[Double, Double] =
  Reader(pi => pi * r * r)

def volumeR(r: Int, h: Int): Reader[Double, Double] =
  areaR(r).map(a => a * h)
val r = 6
val h = 7

val volR = volumeR(r, h) run PI
println(s"volR: ${volR}") // volR: 791.28

The volumeR reader still has an unnecessary dependency on the radius argument, which we can extract as well:

def volumeRR(h: Int): Reader[Double, Reader[Double, Double]] =
  Reader(a => areaR(r).map(a => a * h))
val volRR = volumeRR(h) run r run PI
println(s"volRR: ${volRR}") // volRR: 791.28


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

$ curl -s |
  codedown scala |
  scala-cli -q --scala 3.1.1
vol: 791.28
volR: 791.28
volRR: 791.28