Skip to content

Latest commit

 

History

History
207 lines (166 loc) · 6.26 KB

Reader.md

File metadata and controls

207 lines (166 loc) · 6.26 KB

Reader

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.