case class Reader[E,A](run: E => A)
object Reader:
implicit def readerMonad[E]: Monad[[A] =>> Reader[E,A]] =
new Monad[[A] =>> Reader[E,A]]:
def pure[A](a: A): Reader[E,A] =
Reader(_ => a)
def flatMap[A,B](ma: Reader[E,A])(f: A => Reader[E,B]): Reader[E,B] =
Reader { (e: E) =>
val a: A = ma.run(e)
f(a).run(e)
}
trait LocationService:
def getMyLocation: String
trait WeatherService:
def getWeatherAt(location: String): String
trait LocationServiceIO:
def locationService: LocationService
trait WeatherServiceIO:
def weatherService: WeatherService
def getMyLocation[E <: LocationServiceIO]: Reader[E, String] =
Reader(e => e.locationService.getMyLocation)
def getWeatherAt[E <: WeatherServiceIO](location: String): Reader[E, String] =
Reader(e => e.weatherService.getWeatherAt(location))
import Monad._
import Reader._
type EnvIO = LocationServiceIO with WeatherServiceIO
val getMyWeather: Reader[EnvIO, String] =
for
<- getMyLocation[EnvIO] // requires Scala 2.13 (33478bd)
myLocation <- getWeatherAt[EnvIO](myLocation)
weather yield s"It is ${weather} in ${myLocation}."
val env: EnvIO =
new LocationServiceIO with WeatherServiceIO:
val locationService: LocationService =
new LocationService:
def getMyLocation: String = "Boulder"
val weatherService: WeatherService =
new WeatherService:
def getWeatherAt(location: String): String = "sunny"
val myWeather: String = getMyWeather.run(env)
println(myWeather) // It is sunny in Boulder.
This file is literate Scala, and can be run using Codedown:
$ curl \
https://earldouglas.com/posts/type-classes/applicative.md \
https://earldouglas.com/posts/type-classes/functor.md \
https://earldouglas.com/posts/type-classes/monad.md \
https://earldouglas.com/posts/type-classes/reader.md |
codedown scala |
scala-cli -q --scala 3.1.3 _.sc
It is sunny in Boulder.