Reader
April 8, 2017 ยท View on GitHub
The Reader type is used for providing a shared "environment" to computations.
It is often used as a way of providing configuration or injecting dependencies,
where the responsibility for these concerns is delegated to the outer edges of
an application. You can think of this in much of the same way as how a function
has access to arguments that are provided by the caller of the function. In
fact, the Reader type is effectively a wrapper type around a function to
provide the various monad and functor instances.
Construction
A Reader type can be constructed via the single constructor function
Reader r a :: (r -> a) -> Reader r a. The function given to the constructor is
responsible for computing some value based on the environment it receives.
const dbReader = Reader(env => env.dbConn);
A static Reader instance is also available via Reader.ask which passes its
environment straight through, providing access to the environment within
functions given to its chain method.
const dbReader = Reader.ask.chain(env => Reader.of(configureDb(env.dbConn)));
To provided better support for nested monad types such as Reader r (m a) where
m is some monad type, a monad transformer can be constructed by passing the
nested monad type to the transformer constructor.
// Reader.T :: Monad m => { of: a -> m a } -> ReaderT r m a
const ReaderTMaybe = Reader.T(Maybe);
This wires up the various methods available on a monad (of, map, ap and
chain) from the outer ReaderT instance to the inner monad type, removing the
need to "unpack" each layer of monad instances where the transformer instance is
interacted with.
The static lift method on a ReaderT type allows for an instance of the inner
monad type to be "lifted" into a ReaderT instance, similar to how of will
lift an ordinary value into an applicative instance.
const maybe42R = ReaderTMaybe.lift(Just(42));
Interaction
Code that requires access to the environment needs to exist within a Reader
type. To ensure that code is still composable, Reader implements the monad
specification and by extension, applicative and functor too. This allows for
various Reader instances to be composed with each other, while also being
able to lift plain functions to operate within the context of the Reader type.
/**
* Env :: { dbConn: DbConnection, httpClientPool: HttpClientPool }
* selectOne :: String -> Table -> Object -> DbConnection -> Future Error DbRow
* fetchImage :: URL -> HttpClientPool -> Future Error Image
*/
//:: String -> Reader Env (Future Error DbRow)
fetchUser = email => Reader(env =>
selectOne('email = :email', Users, { email: email }, env.dbConn));
//:: String -> Reader Env (Future Error ImageData)
fetchUserImage = email => Reader.ask.chain(env =>
fetchUser(email).map(futureUserRow => futureUserRow.chain(userRow =>
fetchImage(userRow.imageURL, env.httpClientPool))));
//:: Future Error ImageData
fooImageFuture = fetchUserImage('foo@example.com').run({
dbConn: initDb(),
httpClientPool: initHttpClientPool
});
Alternatively, a ReaderT could be used to help declutter some of the nested
chain and map calls in the fetchUserImage function in the example above.
//:: (Env -> Future e a) -> ReaderT Env (Future e) a
App = Reader.T(Future);
//:: String -> ReaderT Env (Future Error) DbRow
fetchUser = email => App(env =>
selectOne('email = :email', Users, { email: email }, env.dbConn));
//:: String -> ReaderT Env (Future Error) ImageData
fetchUserImage = email => App.ask.chain(env =>
fetchUser(email).chain(userRow =>
App.lift(fetchImage(userRow.imageURL, env.httpClientPool))));
Reference
Constructors
Reader
:: (r -> a) -> Reader r a
Constructs a Reader instance that computes some value a using some
environment r.
Reader.T
:: Monad m => { of: a -> m a } -> (r -> m a) -> ReaderT r m a
Constructs a ReaderT instance that computes some value a within a monad m
using some environment r.
Static properties
Reader.ask
:: Reader r r
A static Reader instance that just returns its environment.
ReaderT.ask
:: Monad m => ReaderT r m a
A static ReaderT instance that just returns its environment.
Static functions
Reader.of
:: a -> Reader r a
Produces a pure Reader instance for the given value.
ReaderT.of
:: Monad m => a -> ReaderT r m a
Produces a pure ReaderT instance for the given value.
ReaderT.lift
:: Monad m => m a -> ReaderT r m a
Lifts a monad value into a ReaderT instance.
Instance methods
reader.run
:: Reader r a ~> r -> a
Executes the Reader instance with the given environment r.
readerT.run
:: Monad m => ReaderT r m a ~> r -> m a
Executes the ReaderT instance with the given environment r.
reader.map
:: Reader r a ~> (a -> b) -> Reader r b
Transforms the result of the computation of the Reader instance with the given
function.
readerT.map
:: Monad m => ReaderT r m a ~> (a -> b) -> ReaderT r m b
Transforms the result of the computation of the ReaderT instance with the
given function.
reader.ap
:: Reader r (a -> b) ~> Reader r a -> Reader r b
Applies the a in the given Reader instance to the function in this Reader
instance, producing a new Reader instance containing the result.
readerT.ap
:: Monad m => ReaderT r m (a -> b) ~> ReaderT r m a -> ReaderT r m b
Applies the a in the given ReaderT instance to the function in this
ReaderT instance, producing a new ReaderT instance containing the result.
reader.chain
:: Reader r a ~> (a -> Reader r b) -> Reader r b
Produces a new Reader instance by applying the provided function with the
value of this Reader instance. Both this instance and the instance returned by
the provided function will receive the same environment when run.
readerT.chain
:: Monad m => ReaderT r m a ~> (a -> ReaderT r m b) -> ReaderT r m b
Produces a new ReaderT instance by applying the provided function with the
value of this ReaderT instance. Both this instance and the instance returned
by the provided function will receive the same environment when run.