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

State

State s a

State is an Algebraic Data Type that abstracts away the associated state management that comes with stateful computations.State is parameterized by two types, a state s and a resultant a. The resultant portion may vary it's type, but the state portion must be fixed to a type that is used by all related stateful computations.

All State instances wrap a function of the form s -> Pair a s and can be constructed by providing a function of this form. In order to get maximum reuse of existing functions, a few construction helpers are available on the State constructor.

State is lazy and is required to be run at the edge with some initial state. Three methods are available on the instance for running the State with a given initial state. runWith will return a Pair a s with the state s on the right and the resultant a on the left.

The other two are used for extracting either the state or resultant, unwrapping the values from the Pair and discarding the unwanted portion.evalWith used when the resultant is wanted, whileexecWith is used to pull the state.

import State from 'crocks/State'

import Pair from 'crocks/Pair'
import constant from 'crocks/combinators/constant'

const { get, put } = State

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

// putResultant :: String -> State String String
const putResultant = x =>
  put(x)
    .map(constant(x))

// standard construction
// State String String
State(s => Pair(toUpper(s), s))
  .runWith('nice')
//=> Pair('NICE', 'nice')

// construction helper
// State String String
get(toUpper)
  .runWith('nice')
//=> Pair('NICE', 'nice')

// combine states
get(toUpper)
  .chain(putResultant)
  .runWith('nice')
//=> Pair('NICE', 'NICE')

// pull resultant only
get(toUpper)
  .evalWith('nice')
//=> 'NICE'

// pull state only
get(toUpper)
  .execWith('nice')
//=> 'nice'

Implements

Functor, Apply, Chain, Applicative, Monad

Construction

State :: (s -> Pair a s) -> State s a

Typically instances of State are constructed using the provided construction helpers like get or put. While the provided helpers fit most situations, there are times where a State instance will need to be constructed "by-hand".

An instance of State can be constructed by passing a unary function that takes some type s as its input and returns a Pair with any type a in the first position and the same type s in second.

While there is nothing that prevents the type s from varying, it is recommended to fix the type to a single type for all State instances that are used in a given flow.

import Pair from 'crocks/Pair'
import State from 'crocks/State'

State(state => Pair(state, state))
//=> State s s

// increaseBy :: Number -> State Number Number
const increaseBy = value =>
  State(state => Pair(value, state + 1))

increaseBy(5)
//=> State Number Number

Constructor Methods

get

State.get :: () -> State s s
State.get :: (s -> a) -> State s a

A construction helper that is used to access the state portion of a given State instance. To make the state accessible, get will place the state in the resultant portion, overwriting what was there previously.

get may be called with or without a function as its argument. When nothing is provided for the argument, the state will be applied to the resultant as is. The state will be mapped over any provided function that takes the same type as the state, with the result deposited in the resultant.

import State from 'crocks/State'

import chain from 'crocks/pointfree/chain'
import compose from 'crocks/helpers/compose'
import getProp from 'crocks/Maybe/getProp'
import isNumber from 'crocks/predicates/isNumber'
import option from 'crocks/pointfree/option'
import safe from 'crocks/Maybe/safe'

const { get } = State

// propOr :: (String, (b -> Boolean), a) -> Object -> c
const propOr = (key, pred, def) =>
  compose(option(def), chain(safe(pred)), getProp(key))

// safeNumber :: Object -> Number
const safeNumber =
  propOr('number', isNumber, 0)

get(safeNumber)
  .runWith({ number: 23 })
//=> Pair(23, { number: 23 })

get(safeNumber)
  .evalWith({ number: '23' })
//=> 0

get()
  .map(safeNumber)
  .evalWith({ number: 23 })
//=> 23

get()
  .map(safeNumber)
  .runWith({ string: '47' })
//=> Pair(0, { string: '47'})

modify

State.modify :: (s -> s) -> State s ()

A construction helper that can be used to lift an endo-function that matches the fixed type of the state portion. The lifted function will receive the state and returns a new State instance with the result of the function in the state portion. Great care should be taken to not use functions that will change the type of the state as it may not be expected in other stateful computations and can result in hard to track down bugs.

import State from 'crocks/State'

import mapProps from 'crocks/helpers/mapProps'

const { modify } = State

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

// addState :: Number -> State Number ()
const addState = x =>
  modify(add(x))

// addValue :: Number -> State Object ()
const addValue = x =>
  modify(mapProps({ value: add(x) }))

addState(5)
  .execWith(45)
//=> 50

addValue(5)
  .execWith({ value: 45 })
//=> { value: 50 }

addValue(5)
  .execWith({})
//=> {}

put

State.put :: s -> State s ()

Used to replace the state portion of a given State instance,, put can be employed anytime that the state can change without having to know about it's previous value. If the previous value is required for a given stateful computation, modify can be used to lift a function that represents the change.

As put updates the state, it is important to ensure that the state portion stays fixed for all related functions. Changing the type of the state portion may result in hard to debug bugs and destroys the relationship between stateful computations.

import State from 'crocks/State'

import compose from 'crocks/helpers/compose'
import isString from 'crocks/predicates/isString'
import option from 'crocks/pointfree/option'
import safe from 'crocks/Maybe/safe'

const { put } = State

// safeString :: a -> String
const safeString =
  compose(option(''), safe(isString))

// reset :: () -> State String ()
const reset = () =>
  put('')

// update :: a ->  State String ()
const update =
  compose(put, safeString)

// heckYeah :: State String ()
const heckYeah =
  update('Oh Heck Yeah')

heckYeah
  .execWith('Gosh')
//=> 'Oh Heck Yeah'

heckYeah
  .chain(reset)
  .runWith('Gosh')
// Pair((), '')

of

State.of :: a -> State s a

Used to "blindly" lift any JavaScript value into a State, of will take the provided value and return back a new State instance with the value in the resultant. There are many uses for of, but mostly it is used to set the resultant in the same way put is used to replace the state. Many times of is used at the start of a given stateful computation or in conjunction with put and modify to replace the Unit the resultant is set to for those construction helpers.

import State from 'crocks/State'

const { get, put } = State

// updatePop :: String -> State String String
const updatePop = x =>
  get().chain(
    old => put(x).chain(
      () => State.of(old)
    )
  )

State.of('hotness')
  .chain(updatePop)
  .runWith('crusty')
//=> Pair('crusty', 'hotness')

Instance Methods

map

State s a ~> (a -> b) -> State s b

While the state portion s of State must remain fixed to a type, the resultant a can vary in it's type as needed. This allows complex stateful computations to be represented with State. The map method provides a means to lift a function into the datatype that will be applied to the resultant and return a new instance of State with the result of the function as the new resultant.

While this is similar to the modify construction helper, which lifts an endo-function that acts upon the state, map does not require an endo-function as it can move to any type.

Due to the composition law associated with map, successive maps can be composed together using function composition. This will give the same results but will only map the value once, instead of once for every mapping.

import State from 'crocks/State'

import compose from 'crocks/helpers/compose'
import objOf from 'crocks/helpers/objOf'
import getPropOr from 'crocks/helpers/getPropOr'

const { get } = State

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

// getNum :: State Object Number
const getNum =
  get(getPropOr(0, 'num'))

getNum
  .map(add(10))
  .evalWith({ num: 32 })
//=> 42

getNum
  .map(add(10))
  .map(objOf('result'))
  .evalWith({ val: 32 })
//=> { result: 10 }

// comp :: Number -> Object
const comp = compose(
  objOf('result'),
  add(10)
)

getNum
  .map(comp)
  .evalWith({ num: 32 })
//=> { result: 42 }

ap

State s (a -> b) ~> State s a -> State s b

Short for apply, the ap method is used to apply the resultant of a given State instance to a function wrapped in another instance. On a State instance that wraps a function, calling ap, providing it another State instance, will return a new State instance with the result of the function in the resultant portion.

When used with curried, polyadic functions, multiple stateful computations can be combined using the lifted function as a means to combine each of the instances' resultants.

import State from 'crocks/State'

import setProp from 'crocks/helpers/setProp'
import getPropOr from 'crocks/helpers/getPropOr'

const { get, modify } = State

const data = {
  tax: .084,
  sub: 34.97
}

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

// multiply :: Number -> Number -> Number
const multiply =
  x => y => x * y

// round :: Number -> Number
const round =
  x => Math.round(x * 100) / 100

// getKey :: String -> State Object Number
const getKey = key =>
  get(getPropOr(0, key))

// updateKey :: String -> a -> State Object ()
const updateKey = key => val =>
  modify(setProp(key, val))

// addToSub :: Number -> String Object Number
const addToSub = x =>
  getKey('sub')
    .map(add(x))

const calcTax =
  getKey('tax')
    .map(multiply)
    .ap(getKey('sub'))

// applyTax :: State Object ()
const applyTax =
  calcTax
    .chain(addToSub)
    .map(round)
    .chain(updateKey('total'))

applyTax
  .execWith(data)
//=> { tax: 0.084, sub: 34.07, total: 37.91 }

chain

State s a ~> (a -> State s b) -> State s b

As a means to combine stateful computations, chain is used to sequence state transactions that either read from or write to the state. chain takes a unary function that must return a new State instance. chain returns a new State instance that will apply the computation when run.

import State from 'crocks/State'

const { get, modify } = State

// add :: Number -> State Number ()
const add = x =>
  modify(y => x + y)

// multiply :: Number -> State Number ()
const multiply = x =>
  modify(y => x * y)

// double :: () -> State Number ()
const double = () =>
  get()
    .chain(add)

// square :: () -> State Number ()
const square = () =>
  get()
    .chain(multiply)

add(10)
  .execWith(10)
//=> 20

add(10)
  .chain(double)
  .execWith(10)
//=> 40

add(10)
  .chain(square)
  .execWith(10)
//=> 400

add(10)
  .chain(double)
  .chain(square)
  .execWith(10)
//=> 1600

runWith

State s a ~> s -> Pair a s

State is a lazy datatype that requires a value for it's state portion to be run. A given State instance provides a runWith method that accepts a value to run the instance with. The value must be a member of the type that the given State instance is fixed to in it's state portion, s.

When called, runWith will run the state transition with the given value as the initial state and will return the resulting Pair with the resultant in the fst (first) and the state in the snd (second).

import State from 'crocks/State'

import K from 'crocks/combinators/constant'

const { get, put } = State

// swap :: s -> s -> State s s
const swap = x => old =>
  put(x)
    .chain(K(State.of(old)))

//update :: s -> State s s
const update = x =>
  get()
    .chain(swap(x))

update(45)
  .runWith(100)
//=> Pair(100, 45)

evalWith

State s a ~> s -> a

State is a lazy datatype that requires a value for it's state portion to be run. A given State instance provides an evalWith method that accepts a value to run the instance with. The value must be a member of the type that the given State instance is fixed to in its state portion, s.

When called, evalWith will run the state transition with the given value as the initial state and will return the resulting resultant discarding the state portion.

import State from 'crocks/State'

import concat from 'crocks/pointfree/concat'
import flip from 'crocks/combinators/flip'
import liftA2 from 'crocks/helpers/liftA2'
import map from 'crocks/pointfree/map'
import getPropOr from 'crocks/helpers/getPropOr'

const { get } = State

const name = {
  first: 'Franklin',
  last: 'Jennings'
}

// getLast :: State Object String
const getFirst =
  get(getPropOr('', 'first'))

// getLast :: State Object String
const getLast =
  get(getPropOr('', 'last'))

// inner :: Functor f => f a -> f [ a ]
const inner =
  map(Array.of)

// combineNames :: State Object [ String ]
const combineNames = liftA2(
  flip(concat),
  inner(getFirst),
  inner(getLast)
)

combineNames
  .evalWith(name)
//=> [ 'Franklin', 'Jennings' ]

execWith

State s a ~> s -> s

State is a lazy datatype that requires a value for it's state portion to be run. A given State instance provides an execWith method that accepts a value to run the instance with. The value must be a member of the type that the given State instance is fixed to in it's state portion, s.

When called, execWith will run the state transition with the given value as the initial state and will return the resulting state, discarding the resultant portion.

import State from 'crocks/State'

import compose from 'crocks/helpers/compose'
import concat from 'crocks/pointfree/concat'

const { modify } = State

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

// exclaim :: String -> String
const exclaim =
  concat('!!!')

// yell :: State String ()
const yell = modify(
  compose(exclaim, toUpper)
)

yell
  .execWith('nice')
//=> 'NICE!!!'

Pointfree Functions

evalWith (pointfree)

crocks/State/evalWith

evalWith :: s -> State s a -> a

The evalWith pointfree function can be employed to execute theevalWith method on a given State instance. This function is typically used at the edge of a program where all the side-effects typically reside.

As all this function does is return the result of applying a given initial state to the evalWith method to the provided State instance, it will also return the resulting resultant, throwing away the resulting state.

import State from 'crocks/State'

import evalWith from 'crocks/State/evalWith'

import compose from 'crocks/helpers/compose'
import curry from 'crocks/helpers/curry'
import flip from 'crocks/combinators/flip'

const { get } = State

// addToState :: Number -> State Number Number
const addToState =
  x => get(y => x + y)

// add :: Number -> Number -> Number
const add = curry(
  compose(flip(evalWith), addToState)
)

// add10 :: Number -> Number
const add10 =
  add(10)

add10(32)
//=> 42

add(1295, 42)
// 1337

execWith (pointfree)

crocks/State/execWith

execWith :: s -> State s a -> s

The execWith pointfree function can be employed to execute theexecWith method on a given State instance. This function is typically used at the edge of a program where all the side-effects typically reside.

As all this function does is return the result of applying a given initial state to the execWith method to the provided State instance, it will also return the resulting state, throwing away the resulting resultant.

import State from 'crocks/State'

import execWith from 'crocks/State/execWith'

import curry from 'crocks/helpers/curry'
import isSameType from 'crocks/predicates/isSameType'
import mapProps from 'crocks/helpers/mapProps'
import when from 'crocks/logic/when'

const { modify } = State

// middleware :: Object -> State Object | Object -> Object
const middleware = curry(
  s => when(isSameType(State), execWith(s))
)

// incValue :: State Object ()
const incValue =
  modify(mapProps({ value: x => x + 1 }))

middleware({ value: 10 }, incValue)
//=> { value: 11 }

middleware({ value: 10 }, { value: 32 })
//=> { value: 32 }

Contribute on Github! Edit this section.