Replace Pass-Through Arguments with the Reader
Monad

Summary

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.

Motivation

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.

Mechanics

We use the following form for our reader monad:

caseclassReader[-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.

Example

val PI =3.14defarea(pi:Double, r:Int):Double= pi * r * rdefvolume(pi:Double, r:Int, h:Int):Double=val a =area(pi, r)val v = a * h v

val vol =volume(PI,6,7)println(s"vol: ${vol}")// vol: 791.28

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

defareaR(r:Int):Reader[Double,Double]=Reader(pi => pi * r * r)defvolumeR(r:Int, h:Int):Reader[Double,Double]=areaR(r).map(a => a * h)

val r =6val h =7val volR =volumeR(r, h) run PIprintln(s"volR: ${volR}")// volR: 791.28

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

defvolumeRR(h:Int):Reader[Double,Reader[Double,Double]]=Reader(a =>areaR(r).map(a => a * h))

val volRR =volumeRR(h) run r run PIprintln(s"volRR: ${volRR}")// volRR: 791.28

Demo

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