Crocks

  • Documentation
Docs Menu
  • Getting Started
  • Crocks
    • Arrow
    • Async
    • Const
    • Either
    • Equiv
    • Identity
    • Maybe
    • Pair
    • Pred
    • Reader
    • ReaderT
    • Result
    • State
    • Tuple
  • Monoids
    • All
    • Any
    • Assign
    • Endo
    • First
    • Last
    • Max
    • Min
    • Prod
    • Sum
  • Functions
    • Combinators
    • Helpers
    • Logic Functions
    • Predicate Functions
    • Point-free Functions
    • Transformation Functions

ReaderT

Monad m => ReaderT e (m a)

ReaderT is a Monad Transformer that wraps a given Monad with a Reader. This allows the interface of a Reader that enables the composition of computations that depend on a shared environment (e -> a), but provides a way to abstract a means the Reader portion, when combining ReaderTs of the same type. All ReaderTs must provide the constructor of the target Monad that is being wrapped.

Implements

Functor, Apply, Chain, Applicative, Monad

Construction

ReaderT :: MonadTypeRep M, Monad m => M -> Reader(M) e (m a)

ReaderT is a type constructor that defines a unary function that takes a constructor, or TypeRep, of any Monad and returns a new constructor that can be used to construct Reader instances that fix the far right portion of the Reader(M) to the provided Monad.

The ReaderT constructor can be thought of as a constructor that constructs a constructor.

import ReaderT from 'crocks/Reader/ReaderT'

import Maybe from 'crocks/Maybe'
import Async from 'crocks/Async'

// MaybeReader :: Reader(Maybe)
const MaybeReader =
  ReaderT(Maybe)

// AsyncReader :: Reader(Async)
const AsyncReader =
  ReaderT(Async)

MaybeReader.of('nice')
  .runWith()
//=> Just "nice"

// AsyncReader e Boolean
AsyncReader.of(true)
  .runWith()
//=> Resolved Boolean

Constructor Methods

ask

ReaderT.ask :: Monad m => () -> ReaderT e (m e)
ReaderT.ask :: Monad m => (e -> a) -> ReaderT e (m a)

A construction helper that returns a ReaderT with environment on the right portion of the Reader. ask can take a function, that can be used to map the environment to a different type or value. When using the function version, the function must return the type of the Monad the ReaderT wraps, which in turn will be wrapped in another

import ReaderT from 'crocks/Reader/ReaderT'
import Maybe from 'crocks/Maybe'

import safe from 'crocks/Maybe/safe'
import isNumber from 'crocks/predicates/isNumber'

const MaybeReader = ReaderT(Maybe)
const { ask } = MaybeReader

// add :: Number -> Number -> Number
const add =
  x => y => x + y

// Typical Constructor
MaybeReader(safe(isNumber))
  .runWith(76)
//=> Just 76

MaybeReader(safe(isNumber))
  .runWith('76')
//=> Nothing

// Using `ask` with no function
// (identity on environment)
ask()
  .runWith(76)
//=> Just 76

ask()
  .runWith('76')
//=> Just '76'

// Using `ask` with a function
// (map environment before deposit)
ask(add(10))
  .runWith(76)
//=> Just 86

lift

ReaderT.lift :: Monad m => m a -> ReaderT e (m a)

Used to promote an instance of a given Monad into a ReaderT of that Monads type. This can be used to lift a pointed instance of the underlying Monad. When mixed with composition, lift can be used to promote functions that take the form of a -> m b into a function that can be chained with the ReaderT. Although, liftFn can be used to remove the composition boilerplate and promote and a -> m b function.

import ReaderT from 'crocks/Reader/ReaderT'
import Async from 'crocks/Async'

import compose from 'crocks/helpers/compose'
import curry from 'crocks/helpers/curry'
import flip from 'crocks/combinators/flip'
import runWith from 'crocks/pointfree/runWith'
import tap from 'crocks/helpers/tap'

const AsyncReader = ReaderT(Async)

const { ask, lift } = AsyncReader
const { Rejected } = Async

// log :: String -> a -> ()
const log = label =>
  console.log.bind(console, label + ':')

// forkLog :: Async a b -> Async a b
const forkLog = tap(
  m => m.fork(log('rej'), log('res'))
)

// runAndLog :: e -> ReaderT e (Async a b) -> Async a b
const runAndLog = curry(
  x => compose(forkLog, flip(runWith, x))
)

// instance :: ReaderT e (Async String a)
const instance =
  lift(Rejected('Always Rejected'))

runAndLog(instance, 'Thomas')
//=> rej: Always Rejected

// Using in a composition
// rejectWith :: a -> ReaderT e (Async a b)
const rejectWith =
  compose(lift, Rejected)

// envReject :: ReadetT e (Async e b)
const envReject =
  ask()
    .chain(rejectWith)

runAndLog(envReject, 'Sammy')
//=> rej: Sammy

liftFn

ReaderT.liftFn :: Monad m => (a -> m b) -> a -> ReaderT e (m b)

Used to transform a given function in the form of a -> m b into a lifted function, where m is the underlying Monad of a ReaderT. This allows for the removal of composition boilerplate that results from using thelift helper.

import ReaderT from 'crocks/Reader/ReaderT'
import Either from 'crocks/Either'

import ifElse from 'crocks/logic/ifElse'

const EitherReader = ReaderT(Either)

const { ask, liftFn } = EitherReader
const { Left, Right } = Either

// gte :: Number -> Number -> Either String Number
const gte = x => ifElse(
  n => n >= x,
  Right,
  n => Left(`${n} is not gte to ${x}`)
)

// gte10 :: Number -> Either String Number
const gte10 =
  gte(10)

// add20 :: ReaderT Number (Either String Number)
const add20 =
  ask()
    .chain(liftFn(gte10))
    .map(n => n + 20)

add20
  .runWith(30)
//=> Right 50

add20
  .runWith(9)
//=> Left "9 is not gte to 10"

of

ReaderT.of :: Monad m => a -> ReaderT e (m a)

Lifts a value into a ReaderT using the of method of the underlying Monad.of will disregard the environment and points the right portion to the provided value.

import ReaderT from 'crocks/Reader/ReaderT'

import Maybe from 'crocks/Maybe'
import Either from 'crocks/Either'
import State from 'crocks/State'

const MaybeReader = ReaderT(Maybe)
const EitherReader = ReaderT(Either)
const StateReader = ReaderT(State)

MaybeReader.of('yep')
  .map(x => x.toUpperCase())
  .runWith(23)
//=> Just "YEP"

EitherReader.of(43)
  .runWith(23)
//=> Right 43

StateReader.of(0)
  .runWith(23)
  .runWith(42)
//=> Pair(0, 42)

Instance Methods

map

Monad m => ReaderT e (m a) ~> (a -> b) -> ReaderT e (m b)

Provides a means for lifting a normal javascript function into the underlying Monad, allowing the innermost value of the underlying Monad to be mapped. This method will ignore the outer ReaderT, and be applied directly to the underlying Monad.

import ReaderT from 'crocks/Reader/ReaderT'
import Maybe from 'crocks/Maybe'

import isString from 'crocks/predicates/isString'
import safe from 'crocks/Maybe/safe'

const MaybeReader =
  ReaderT(Maybe)

const { ask, liftFn } = MaybeReader

// maybeString :: a -> Maybe String
const maybeString =
  safe(isString)

// toUpper :: String -> String
const toUpper =
  x => x.toUpperCase()

// envToUpper :: ReaderT e (Maybe String)
const envToUpper =
  ask()
    .chain(liftFn(maybeString))
    .map(toUpper)

envToUpper
  .runWith(4)
//=> Nothing

envToUpper
  .runWith('hola')
//=> Just "HOLA"

ap

Monad m => ReaderT e (m (a -> b)) ~> ReaderT e (m a) -> ReaderT e (m b)

Applies wrapped functions to the provided value, using the ap of the underlying Monad. A ReaderT of the underlying Monad must be provided, which allows access to the environment.

import Pair from 'crocks/Pair'
import ReaderT from 'crocks/Reader/ReaderT'
import Result from 'crocks/Result'

import fst from 'crocks/Pair/fst'
import snd from 'crocks/Pair/snd'

import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import liftA2 from 'crocks/helpers/liftA2'

const { Err, Ok } = Result

const ResultReader =
  ReaderT(Result)

const { ask, liftFn } = ResultReader

// add :: Number -> Number -> Number
const add =
  x => y => x + y

// makeError :: a -> Result [ String ] b
const makeErr =
  x => Err([ `${x} is not a Number` ])

// isValid :: a -> ReaderT e (Result [ String ] Number)
const isValid = liftFn(
  ifElse(isNumber, Ok, makeErr)
)

// first :: ReaderT (Pair a b) (Result [ String ] Number)
const first =
  ask(fst)
    .chain(isValid)

// second :: ReaderT (Pair a b) (Result [ String ] Number)
const second =
  ask(snd)
    .chain(isValid)

// Using a fluent style with of
ResultReader.of(add)
  .ap(first)
  .ap(second)
  .runWith(Pair(34, 21))
//=> Ok 55

// Using a fluent style with map
first
  .map(add)
  .ap(second)
  .runWith(Pair(true, 21))
//=> Err [ "true is not a Number" ]

// Using liftA2
liftA2(add, first, second)
  .runWith(Pair('Bob', 'Jones'))
//=> Err [ 'Bob is not a Number', 'Jones is not a Number' ]

chain

Monad m => ReaderT e (m a) ~> Reader e (a -> ReaderT e (m b)) -> ReaderT e (m b)

Can be used to apply the effects of the underlying Monad with the benefit of being able to read from the environment. This method only accepts functions of the form Monad m => a -> ReaderT e (m b).

import ReaderT from 'crocks/Reader/ReaderT'
import Maybe from 'crocks/Maybe'
import getProp from 'crocks/Maybe/getProp'

const MaybeReader =
  ReaderT(Maybe)

const { ask, liftFn } = MaybeReader

// readProp :: String -> b -> ReaderT e (Maybe a)
const readProp = key =>
  liftFn(getProp(key))

// getName :: ReaderT e (Maybe a)
const getName =
  ask()
    .chain(readProp('name'))

// getFirstName :: ReaderT e (Maybe a)
const getFirstName =
  getName
    .chain(readProp('first'))

// getLastName :: ReaderT e (Maybe a)
const getLastName =
  getName
    .chain(readProp('last'))

// person :: Object
const person = {
  name: {
    first: 'Hazel',
    middle: 'Anne'
  }
}

getFirstName
  .runWith(person)
//=> Just "Hazel"

getLastName
  .runWith(person)
//=> Nothing

getLastName
  .runWith(10)
//=> Nothing

runWith

Monad m => ReaderT e (m a) ~> e -> m a

In order to unwrap the underlying Monad, ReaderT needs to be ran with a given environment. A ReaderT instance comes equipped with a runWith method that accepts an environment and returns the resulting Monad.

import ReaderT from 'crocks/Reader/ReaderT'
import Maybe from 'crocks/Maybe'

import getProp from 'crocks/Maybe/getProp'

const MaybeReader = ReaderT(Maybe)
const { ask, liftFn } = MaybeReader

// data :: Object
const data = {
  animals: [
    'tiger', 'muskrat', 'mouse'
  ]
}

// length :: Array -> Number
const length =
  x => x.length

// prop :: String -> ReaderT Object (Maybe [])
const prop = key =>
  ask()
    .chain(liftFn(getProp(key)))

prop('animals')
  .map(length)
  .runWith(data)
//=> Just 3

Contribute on Github! Edit this section.