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

Result

Result e a = Err e | Ok a

Result is a Sum Type similar to that of Either with the added behaviour of accumulating the Err when using ap or alt. With Either being defined as a tagged union type, it captures the essence of disjunction as it provides either a Left value or a Right value but not both. With a Result the left contains the error information from an operation and the right contains the result.Result is well suited for capturing disjunction when the cause of the "error" case needs to be communicated. For example, when executing a function and you exception is important or useful.

A Result represents disjunction by using two constructors, Err and Ok. An Ok instance represents the positive result while Err is considered the negative. With the exception ofcoalesce, swap and bimap, all Result returning methods on an instance will be applied to an Ok returning the result. If an instance is an Err, then all application is skipped and another Err instance is returned with the same containing value.

It is recommended to use the available Ok and Err constructors to construct Result instances in most cases. You can use the Result constructor to construct an Ok, but it will read better to just use Ok.

import Result from 'crocks/Result'

import and from 'crocks/logic/and'
import bimap from 'crocks/pointfree/bimap'
import composeB from 'crocks/combinators/composeB'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import liftA2 from 'crocks/helpers/liftA2'

const { Err, Ok } = Result

// gte :: Number -> Number -> Boolean
const gte = y => x =>
  x >= y

// lte :: Number -> Number -> Boolean
const lte = y => x =>
  x <= y

// between :: (Number, Number) -> Boolean
const between = (x, y) =>
  and(gte(x), lte(y))

// ensure :: (a -> Boolean) -> a -> Result a
const ensure = pred =>
  ifElse(pred, Ok, Err)

// inRange :: Number -> Result
const inRange =
  ensure(between(10, 15))

inRange(12)
//=> Ok 12

inRange(25)
//=> Err 25

inRange('Test')
//=> Err "Test"

// ensureNumber :: a -> Result [a] a
const ensureNumber = composeB(
  bimap(x => [ x ], x => x),
  ensure(isNumber)
)

// prod :: Number -> Number -> Number
const prod = a => b =>
  a * b

ensureNumber('Not a number 1')
  .alt(ensureNumber('Not a number 2'))
//=> Err [ "Not a number 1", "Not a number 2" ]

liftA2(
  prod,
  ensureNumber('Not 21'),
  ensureNumber('Not 2')
)
//=> Err [ "Not 21", "Not 2" ]

liftA2(
  prod,
  ensureNumber(21),
  ensureNumber(2)
)
//=> Ok 42

Implements

Setoid, Semigroup, Functor, Alt, Apply, Traversable,Chain, Applicative, Monad

Construction

Result :: a -> Result e a

Most of the time, Result is constructed using functions of your own making and helper functions like tryCatch or by employing one of the instance constructors, Ok or Err. This is due to the nature of Result and most other Sum Types.

As a matter of consistency and completion, a Result instance can also be constructed using its TypeRep like any other type. The Result constructor is a unary function that accepts any type a and returns a Ok instance, wrapping the value passed to its argument.

import Result from 'crocks/Result'

import equals from 'crocks/pointfree/equals'

const { Ok, Err, of } = Result

Result('some string')
//=> Ok "some string"

Result(null)
//=> Ok null

Result(undefined)
//=> Ok undefined

of('some string')
//=> Ok "some string"

of(null)
//=> Ok null

of(undefined)
//=> Ok undefined

Ok('some string')
//=> Ok "some string"

Ok(null)
//=> Ok null

Ok(undefined)
//=> Ok undefined

Err('some string')
//=> Err "some string"

Err(null)
//=> Err null

Err(undefined)
//=> Err undefined

equals(
  Result.Ok([ 1, 2, 3 ]),
  Result.of([ 1, 2, 3 ])
)
//=> true

equals(
  of({ a: 100 }),
  Result({ a: 100 })
)
//=> true

Constructor Methods

Err

Result.Err :: e -> Result e a

Used to construct an Err instance that represents the "false" portion of a disjunction. When an instance is an Err, most Result returning methods will just return a new Err instance with the same containing value.

The power of the Err as opposed to a Left or a Nothing is that it can hold meaningful information on why the flow is in this path. It will also accumulate this information when ap or alt are used. This works as a core tool when using Railway Orientated Programming concepts.

import Result from 'crocks/Result'

import bimap from 'crocks/pointfree/bimap'
import chain from 'crocks/pointfree/chain'
import composeB from 'crocks/combinators/composeB'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'

const { Ok, Err } = Result

// ensure :: (a -> Boolean) -> a -> Result a
const ensure = pred =>
  ifElse(pred, Ok, Err)

// buildError :: () -> String
const buildError = () =>
  'The value given was not a valid number'

// add10 :: Number -> Number
const add10 =
  x => x + 10

// protectedAdd10 :: a -> Result String Number
const protectedAdd10 = composeB(
  bimap(buildError, add10),
  ensure(isNumber)
)

Err(undefined)
//=> Err undefined

Err(null)
//=> Err null

Err(23)
  .map(add10)
//=> Err 23

Ok(23)
  .map(add10)
//=> Ok 33

chain(protectedAdd10, Err('number'))
//=> Err "number"

chain(protectedAdd10, Ok(10))
//=> Ok 20

Ok

Result.Ok :: a -> Result e a

Used to construct an Ok instance that represents the "true" portion of a disjunction or a valid value. Ok will wrap any given value in anOk, signaling the validity of the wrapped value.

import Result from 'crocks/Result'

import bimap from 'crocks/pointfree/bimap'
import composeB from 'crocks/combinators/composeB'
import identity from 'crocks/combinators/identity'
import ifElse from 'crocks/logic/ifElse'
import isString from 'crocks/predicates/isString'
import map from 'crocks/pointfree/map'

const { Ok, Err } = Result

// ensure :: (a -> Boolean) -> a -> Result a
const ensure = pred =>
  ifElse(pred, Ok, Err)

// buildError :: () -> String
const buildError = () =>
  'The value given is not a valid string'

// ensureString :: a -> Result String Number
const ensureString = composeB(
  bimap(buildError, identity),
  ensure(isString)
)

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

Ok(32)
//=> Ok 32

Ok(undefined)
//=> Ok undefined

Ok(null)
//=> Ok null

// safeShout :: a -> Result String
const safeShout = composeB(
  map(toUpper),
  ensureString
)

safeShout(45)
//=> Err "The value given is not a valid string"

safeShout('Hey there!')
//=> Ok "HEY THERE!"

of

Result.of :: a -> Result e a

Used to wrap any value into a Result as an Ok, of is used mostly by helper functions that work "generically" with instances of either Applicative or Monad types. When working specifically with the Result type, the Ok constructor should be used. Reach for of when working with functions that will work with ANY Applicative/Monad.

import Result from 'crocks/Result'

const { Ok, of } = Result

of('Some result!')
//=> Ok "Some result!"

of(undefined)
//=> Ok undefined

Ok('Some result!')
//=> Ok "Some result!"

Ok(undefined)
//=> Ok undefined

Result('Some result!')
//=> Ok "Some result!"

Result(undefined)
//=> Ok undefined

Instance Methods

equals

Result e a ~> b -> Boolean

Used to compare the contained values of two Result instances for equality by value. equals takes any given argument and returns true if the passed argument is a Result (Ok or Err) with a contained value equal to the contained value of the Result the method is being called on. If the passed argument is not a Result or the contained values are not equal by value then equals will return false.

import Result from 'crocks/Result'

import equals from 'crocks/pointfree/equals'

const { Ok, Err } = Result

Ok('result')
  .equals(Ok('result'))
//=> true

Ok(null)
  .equals(Ok(null))
//=> true

Ok('error')
  .equals(Err('error'))
//=> false

// by value, not reference for most types
Ok([ 1, { a: 2 }, 'string' ])
  .equals(Ok([ 1, { a: 2 }, 'string' ]))
//=> true

equals(Ok('result'), 'result')
//=> false

concat

Semigroup s => Result e s ~> Result e s -> Result e s

When an underlying value of a given Result is fixed to a Semigroup, concat can be used to concat another Result instance with an underlying Semigroup of the same type. Expecting a Result wrapping a Semigroup of the same type, concat will give back a new Result instance wrapping the result of combining the two underlying Semigroups. When called on a Err instance, concat will return an Err with the value of the first Err in the chain.

import Result from 'crocks/Result'

import concat from 'crocks/pointfree/concat'

const { Ok, Err } = Result

Ok([ 1, 2, 3 ])
  .concat(Ok([ 4, 5, 6 ]))
//=> [ 1, 2, 3, 4, 5, 6 ]

Ok([ 1, 2, 3 ])
  .concat(Err([ 4, 5, 6 ]))
//=> Err [ 4, 5, 6 ]

concat(Ok('Result'), Err('Error'))
//=> Err "Error"

concat(Err('Error'), Ok('Result'))
//=> Err "Error"

concat(Err('Error 1'), Err('Error 2'))
//=> Err "Error 2"

map

Result e a ~> (a -> b) -> Result e b

Used to apply transformations to values in the safety of a Result, map takes a function that it will lift into the context of the Result and apply to it the wrapped value. When run on an Ok instance, map will apply the wrapped value to the provided function and return the result in a new Okinstance. When run on an 'Err' map with return the error value in a newErr instance.

import Result from 'crocks/Result'

import assign from 'crocks/helpers/assign'
import compose from 'crocks/helpers/compose'
import composeB from 'crocks/combinators/composeB'
import fanout from 'crocks/Pair/fanout'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import map from 'crocks/pointfree/map'
import merge from 'crocks/pointfree/merge'
import objOf from 'crocks/helpers/objOf'

const { Ok, Err } = Result

// buildError :: () -> Result String a
const buildError = () =>
  Err('The value given was not a valid number')

// double :: Number -> Number
const double =
  x => x * 2

// fromNumber :: a -> Result String Number
const fromNumber =
  ifElse(isNumber, Ok, buildError)

// doubleNumber :: a -> Result String Number
const doubleNumber = composeB(
  map(double),
  fromNumber
)

doubleNumber(21)
//=> Ok 42

doubleNumber('down')
//=> Err "The value given was not a valid number"

// isEvenOrOdd :: Number -> String
const isEvenOrOdd = n =>
  n % 2 === 0 ? 'Even' : 'Odd'

// getParity :: Number -> Object
const getParity = composeB(
  objOf('parity'),
  isEvenOrOdd
)

// getInfo :: a -> Result String ({ parity: String, value: Number })
const getInfo = compose(
  map(merge(assign)),
  map(fanout(objOf('value'), getParity)),
  fromNumber
)

getInfo(5324)
//=> Ok { parity: "Even", value: 5324 }

getInfo('down')
//=> Err "The value given was not a valid number"

alt

Result e a ~> Result e a -> Result e a

Providing a means for a fallback or alternative value, alt combines two Result instances and will return the first Ok it encounters or am Err if neither value is an Ok.

If the value in both Err are Semigroups of the same type then they will accumulate based on their rules.

import Result from 'crocks/Result'

import alt from 'crocks/pointfree/alt'
import compose from 'crocks/helpers/compose'
import curry from 'crocks/core/curry'
import flip from 'crocks/combinators/flip'
import identity from 'crocks/combinators/identity'
import ifElse from 'crocks/logic/ifElse'
import map from 'crocks/pointfree/map'
import reduce from 'crocks/pointfree/reduce'
import bimap from 'crocks/pointfree/bimap'

const { Ok, Err } = Result

// ensure :: (a -> Boolean) -> a -> Result a
const ensure = pred =>
  ifElse(pred, Ok, Err)

// gte :: Number -> Number -> Boolean
const gte = x => y =>
  y >= x

// find :: (a -> Boolean) -> Foldable a -> Result String a
const find = curry(
  pred => compose(
    bimap(() => 'Not Found', identity),
    reduce(flip(alt), Err()),
    map(ensure(pred))
  )
)

Err('Error')
  .alt(Ok('Result'))
//=> Ok "Result"

Ok('Result')
  .alt(Err('Error'))
//=> Ok "Result"

Err('Error 1.')
  .alt(Err('Error 2.'))
//=> Err "Error 1.Error 2."

find(gte(41), [ 17, 25, 38, 42 ])
//=> Ok 42

find(gte(11), [ 1, 2, 3, 4 ])
//=> Err "Not found"

bimap

Result e a ~> ((e -> d), (a -> b)) -> Result d b

While it's more common to only map over a Result that's anOk there may come a time when you need to map over a Result regardless of whether it's an Ok or an Err.

bimap takes two mapping functions as its arguments. The first function is used to map an Err instance, while the second maps an Ok.Result only provides a means to map an Ok instance exclusively usingmap. If the need arises to map an Err instance exclusively, then bimap can be used, passing the mapping function to the first argument and identity to the second.

import Result from 'crocks/Result'

import bimap from 'crocks/pointfree/bimap'
import composeB from 'crocks/combinators/composeB'
import identity from 'crocks/combinators/identity'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import map from 'crocks/pointfree/map'
import objOf from 'crocks/helpers/objOf'
import setProp from 'crocks/helpers/setProp'

const { Ok, Err } = Result

// ensure :: (a -> Boolean) -> a -> Result a
const ensure = pred =>
  ifElse(pred, Ok, Err)

// buildError :: () -> String
const buildError = () =>
  'The value given was not a valid number'

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

// fromNumber :: a -> Result String Number
const fromNumber = composeB(
  bimap(buildError, identity),
  ensure(isNumber)
)

// hasError :: Boolean -> Object -> Object
const hasError =
  setProp('hasError')

// Outcome :: { result: Number, hasError: Boolean, error: String }

// buildResult :: (String, Boolean) -> a -> Outcome
const buildResult = (key, isError) =>
  composeB(hasError(isError), objOf(key))

// finalize :: Result e a -> Result Outcome
const finalize = bimap(
  buildResult('error', true),
  buildResult('result', false)
)

// double :: a -> Result String Number
const double = composeB(
  map(prod(2)),
  fromNumber
)

finalize(double(21))
//=> Ok { hasError: false, result: 42 }

finalize(double('unk'))
//=> Err { hasError: true, error: "The value given was not a valid number" }

ap

Result e (a -> b) ~> Result e a -> Result e b

Short for apply, ap is used to apply a Result instance containing a value to another Result instance that contains a function, resulting in new Result instance with the result. ap requires that it is called on an instance that is either an Err or an Ok that wraps a curried polyadic function.

When either instance is an Err, ap will return an Err. This can be used to safely combine multiple values under a given combination function. If any of the inputs results in an Err than they will never be applied to the function and not provide exceptions or unexpected results. However if the value in both Err are Semigroups of the same type then they will accumulate based on their rules.

import Result from 'crocks/Result'

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

const { Ok, Err } = Result

// buildError :: () -> Result String a
const buildError = () =>
  Err('The value given was not a valid number')

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

// fromNumber :: a -> Result String Number
const fromNumber =
  ifElse(isNumber, Ok, buildError)

map(prod, fromNumber(2))
  .ap(fromNumber(5))
//=> Ok 10

map(prod, fromNumber('string'))
  .ap(fromNumber(5))
//=> Err "The value given was not a valid number"

Ok(prod)
  .ap(fromNumber(2))
  .ap(fromNumber(21))
//=> Ok 42

Ok(prod)
  .ap(fromNumber('string'))
  .ap(fromNumber(21))
//=> Err "The value given was not a valid number"

liftA2(prod, fromNumber(2), fromNumber(21))
//=> Ok 42

liftA2(prod, Err('Not 2'), Err('Not 21'))
//=> Err "Not 2Not 21"

liftA2(prod, Err([ 'Not 2' ]), Err([ 'Not 21' ]))
//=> Err [ "Not 2", "Not 21" ]

sequence

Apply f => Result e (f a) ~> (b -> f b) -> f (Result e a)
Applicative f => Result e (f a) ~> TypeRep f -> f (Result e a)

When an instance of Result wraps an Apply instance, sequence can be used to swap the type sequence. sequence requires either an Applicative TypeRep or an Apply returning function is provided for its argument. This will be used in the case that the Result instance is a Err.

sequence can be derived from traverse by passing it an identity function (x => x).

import Result from 'crocks/Result'

import Identity from 'crocks/Identity'

const { Err, Ok } = Result

// arrayOf :: a -> [ a ]
const arrayOf =
  x => [ x ]

Ok([ 1, 2, 3 ])
  .sequence(arrayOf)
//=> [ Ok 1, Ok 2, Ok 3 ]

Err('no array here')
  .sequence(arrayOf)
//=> [ Err "no array here" ]

Ok(Identity.of(42))
  .sequence(Identity)
//=> Identity (Ok 42)

Err(0)
  .sequence(Identity)
//=> Identity (Err 0)

traverse

Apply f => Result e a ~> (c -> f c), (a -> f b)) -> f Result e b
Applicative f => Result e a ~> (TypeRep f, (a -> f b)) -> f Result e b

Used to apply the "effect" of an Apply to a value inside of a Result,traverse combines both the "effects" of the Apply and the Result by returning a new instance of the Apply, wrapping the result of the Applys "effect" on the value in the Result.

traverse requires either an Applicative TypeRep or an Apply returning function as its first argument and a function that is used to apply the "effect" of the target Apply to the value inside of the Result. This will be used in the case that the Result instance is a Err. Both arguments must provide an instance of the target Apply.

import Result from 'crocks/Result'

import Pair from 'crocks/Pair'
import State from 'crocks/State'
import Sum from 'crocks/Sum'
import constant from 'crocks/combinators/constant'
import ifElse from 'crocks/logic/ifElse'
import traverse from 'crocks/pointfree/traverse'

const { Err, Ok } = Result
const { get, modify } = State

// lte :: Number -> Number -> Boolean
const lte = y => x =>
  x <= y

// ensure :: (a -> Boolean) -> a -> Result a
const ensure = pred =>
  ifElse(pred, Ok, Err)

// tallyOf :: Number -> Pair Sum Number
const tallyOf = x =>
  Pair(Sum.empty(), x)

// incBy :: Number -> Pair Sum Number
const incBy = x =>
  Pair(Sum(x), x)

Ok(12)
  .traverse(tallyOf, incBy)
//=> Pair( Sum 12, Ok 12 )

Err(true)
  .traverse(tallyOf, incBy)
//=> Pair( Sum 0, Err true )

// lte10 :: Number -> Result Number
const lte10 =
  ensure(lte(10))

// update :: Number -> State Number
const update = x =>
  modify(state => x + state)
    .chain(constant(get()))

// updateSmall :: () => State Number
const updateSmall = () =>
  get(lte10)
    .chain(traverse(State, update))

updateSmall()
  .runWith(3)
//=> Pair( Ok 6, 6 )

updateSmall()
  .chain(updateSmall)
  .runWith(3)
//=> Pair( Ok 12, 12 )

updateSmall()
  .chain(updateSmall)
  .chain(updateSmall)
  .runWith(3)
//=> Pair( Err 12, 12 )

updateSmall()
  .chain(updateSmall)
  .chain(updateSmall)
  .runWith(30)
//=> Pair( Err 30, 30 )

chain

Result e a ~> (a -> Result e b) -> Result e b

Combining a sequential series of transformations that capture disjunction can be accomplished with chain. chain expects a unary, Result returning function as its argument. When invoked on an Err, chain will not run the function, but will instead return a new Err instance with the same containing value. When called on an Ok however, the inner value will be passed to provided function, returning the result as the new instance.

import Result from 'crocks/Result'

import chain from 'crocks/pointfree/chain'
import composeB from 'crocks/combinators/composeB'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import maybeToResult from 'crocks/Result/maybeToResult'
import map from 'crocks/pointfree/map'
import getProp from 'crocks/Maybe/getProp'

const { Err, Ok } = Result

// errText :: (String | Number) -> String
const errText = name =>
  `${name} does not exist on the value given`

// buildError :: () -> Result String a
const buildError = () =>
  Err('the value given is not valid')

// ensure :: (a -> Boolean) -> a -> Result String a
const ensure = pred =>
  ifElse(pred, Ok, buildError)

// fromNumber :: a -> Result String Number
const fromNumber =
  ensure(isNumber)

// prop :: (String | Number) -> Object -> Result String a
const prop = name =>
  maybeToResult(errText(name), getProp(name))

// protectedAdd10 :: a -> Result String Number
const protectedAdd10 = composeB(
  map(x => x + 10),
  fromNumber
)

// getAge :: Object -> Result String Number
const getAge = composeB(
  chain(fromNumber),
  prop('age')
)

getAge({ name: 'Sarah', age: 21 })
//=> Ok 21

getAge({ name: 'Sarah', age: 'unk' })
//=> Err "the value given is not valid"

getAge({ name: 'Sarah' })
//=> Err "age does not exist on the value given"

getAge({ name: 'Sarah', age: 21 })
  .chain(protectedAdd10)
//=> Ok 31

getAge({ name: 'Sarah', age: 'unk' })
  .chain(protectedAdd10)
//=> Err "the value given is not valid"

coalesce

Result e a ~> ((e -> b), (a -> b))) -> Result c b

There will come a time in your flow that you will want to ensure you have an Ok of a given type. coalesce allows you to map over both the Ok and the Err and return an Ok. coalesce expects two functions for it's inputs.

The first function is used when invoked on a Err and will return a Ok instance wrapping the result of the function. The second function is used when coalesce is invoked on a Ok and is used to map the original value, returning a new Ok instance wrapping the result of the second function.

import Result from 'crocks/Result'

import assign from 'crocks/helpers/assign'
import chain from 'crocks/pointfree/chain'
import coalesce from 'crocks/pointfree/coalesce'
import compose from 'crocks/helpers/compose'
import composeB from 'crocks/combinators/composeB'
import constant from 'crocks/combinators/constant'
import fanout from 'crocks/Pair/fanout'
import hasProp from 'crocks/predicates/hasProp'
import identity from 'crocks/combinators/identity'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import isObject from 'crocks/predicates/isObject'
import map from 'crocks/pointfree/map'
import merge from 'crocks/pointfree/merge'
import objOf from 'crocks/helpers/objOf'
import setProp from 'crocks/helpers/setProp'

const { Err, Ok } = Result

// gte :: Number -> Number -> Boolean
const gte = y => x =>
  x >= y

// ensure :: (a -> Boolean) -> a -> Result a
const ensure = pred =>
  ifElse(pred, Ok, Err)

// fromNumber :: a -> Result a Number
const fromNumber =
  ensure(isNumber)

// ensureIsObject :: a -> Result a Object
const ensureIsObject =
  ensure(isObject)

fromNumber(45)
  .coalesce(constant(42), identity)
//=> Ok 45

fromNumber('number')
  .coalesce(constant(42), identity)
//=> Ok 42

// hasAge :: a -> Result a Boolean
const hasAge =
  ensure(hasProp('age'))

// prop :: String -> Object -> a
const prop = name => x =>
  x[name]

// Person :: { canDrink: Boolean, name: String: age: Number }

// ensureHasAge :: a -> Result a Person
const ensureHasAge = compose(
  coalesce(setProp('age', 0), identity),
  chain(hasAge),
  coalesce(constant({}), identity),
  ensureIsObject
)

// determineCanDrink :: Number -> { canDrink: Boolean }
const determineCanDrink =
  composeB(objOf('canDrink'), gte(18))

// setCanDrink :: a -> Result a Person
const setCanDrink = compose(
  merge(assign),
  map(determineCanDrink),
  fanout(identity, prop('age'))
)

// setCanDrink :: a -> Result Person
const getDetails = composeB(
  map(setCanDrink),
  ensureHasAge
)

getDetails({ name: 'John', age: 17 })
//=> Ok { canDrink: false, name: "John", age: 17 }

getDetails({ name: 'Laury', age: 22 })
//=> Ok { canDrink: true, name: "Laury", age: 22 }

getDetails(null)
//=> Ok { canDrink: false, age: 0 }

getDetails(undefined)
//=> Ok { canDrink: false, age: 0 }

getDetails(1)
//=> Ok { canDrink: false, age: 0 }

getDetails(false)
//=> Ok { canDrink: false, age: 0 }

bichain

Result c a ~> ((c -> Result d b), (a -> Result d b)) -> Result d b

Combining a sequential series of transformations that capture disjunction can be accomplished with chain. Along the same lines, bichain allows you to do this from both Err and Ok. bichain expects two unary, Either returning functions as its arguments. When invoked on an Err instance, bichain will use the left, or first, function that can return either an Err or an Ok instance. When called on an Ok instance, it will behave exactly as chain would with the right, or second, function.

import Result from 'crocks/Result'

import bichain from 'crocks/pointfree/bichain'
import compose from 'crocks/helpers/compose'
import composeK from 'crocks/helpers/composeK'
import constant from 'crocks/combinators/constant'
import equals from 'crocks/pointfree/equals'
import getProp from 'crocks/Maybe/getProp'
import isNumber from 'crocks/predicates/isNumber'
import map from 'crocks/pointfree/map'
import maybeToResult from 'crocks/Result/maybeToResult'
import objOf from 'crocks/helpers/objOf'
import safe from 'crocks/Maybe/safe'
import substitution from 'crocks/combinators/substitution'
import tryCatch from 'crocks/Result/tryCatch'

const { Err, Ok } = Result

// swapResult :: Result e a -> Result a e
const swapResult =
  bichain(Ok, Err)

swapResult(Err('err'))
//=> Ok "err"

swapResult(Ok('ok'))
//=> Err "ok"

// Wrapped :: { value: Number }
// wrapEven :: Number -> Wrapped
function wrapEven(num) {
  if(!isNumber(num)) {
    throw new TypeError('wrapEven: Must be a Number')
  }

  if(num % 2) {
    throw new Error('wrapEven: Nunber must be even')
  }

  return { value: num }
}

// increment :: Number -> Number
const increment = x =>
  x + 1

const checkEvenError = composeK(
  safe(equals('wrapEven: Nunber must be even')),
  getProp('message')
)

// alwaysWrapInc :: Number -> () -> Wrapped
const alwaysWrapInc = compose(
  constant,
  objOf('value'),
  increment
)

// makeWrappedEven :: Number -> Error -> Maybe Wrapped
const makeWrappedEven = orig => compose(
  map(alwaysWrapInc(orig)),
  checkEvenError
)

// resolveError :: Number -> Error -> Result Error Wrapped
const resolveError = compose(
  substitution(maybeToResult),
  makeWrappedEven
)

// safeWrapEven :: Number -> Result Error Wrapped
const safeWrapEven = num =>
  tryCatch(wrapEven, num)
    .bichain(resolveError(num), Ok)

safeWrapEven(2)
//=> Ok { value: 2 }

safeWrapEven(42)
//=> Ok { value: 42 }

safeWrapEven(23)
//=> Ok { value: 24 }

safeWrapEven('92')
//=> Err TypeError: wrapEven: Must be a Number

safeWrapEven(null)
//=> Err TypeError: wrapEven: Must be a Number

swap

Result e a ~> ((e -> a), (a -> e)) -> Result e a

Used to map the value of a Result instance and transform an Err into anOk or an Ok into an Err, swap takes two functions as its arguments. The first function is used to map and transform an Err into an Ok, while the second maps and transforms an Ok into an Err. If no mapping of the contained values is required for either instance, then identity functions can be used in one or both arguments.

import Result from 'crocks/Result'

import constant from 'crocks/combinators/constant'
import identity from 'crocks/combinators/identity'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import when from 'crocks/logic/when'
import swap from 'crocks/pointfree/swap'

const { Ok, Err } = Result

// simpleSwap :: Result e a -> Result a e
const simpleSwap =
  swap(identity, identity)

simpleSwap(Ok(42))
//=> Err 42

simpleSwap(Err(21))
//=> Ok 21

// ensure :: (a -> Boolean) -> a -> Result String a
const ensure = pred =>
  ifElse(pred, Ok, Err)

// fromNumber :: a -> Result String Number
const fromNumber =
  ensure(isNumber)

// toString :: Number -> String
const toString = x =>
  x.toString()

// toNumber :: String -> Number
const toNumber = x =>
  parseInt(x)

// parseWithDefault :: Number -> a -> Number
const parseWithDefault = defaultValue => value =>
  when(isNaN, constant(defaultValue), toNumber(value))

// swapWith :: Result String Number -> Result String Number
const swapValues =
  swap(parseWithDefault(0), toString)

swapValues(fromNumber(4))
//=> Err "4"

swapValues(fromNumber('number'))
//=> Ok 0

either

Result e a ~> ((e -> b), (a -> b)) -> b

Used to provide a means to map a given Result instance folding it out of its container. either expects two functions as its arguments. The first is a function that will be used to map an Err. While the second will map the value wrapped in a given Ok and return the result of that mapping.

By composing either you can create functions that us the power of ADTs while returning a plain javascript type.

import Result from 'crocks/Result'

import compose from 'crocks/helpers/compose'
import either from 'crocks/pointfree/either'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import map from 'crocks/pointfree/map'
import objOf from 'crocks/helpers/objOf'
import setProp from 'crocks/helpers/setProp'

const { Ok, Err } = Result

// buildError :: () -> Result String a
const buildError = () =>
  Err('The value given was not a valid number')

// ensure :: (a -> Boolean) -> a -> Result String a
const ensure = pred =>
  ifElse(pred, Ok, buildError)

// fromNumber :: a -> Result String Number
const fromNumber =
  ensure(isNumber)

// double :: Number -> Number
const double = x =>
  x * 2

// hasError :: Boolean -> Object -> Object
const hasError =
  setProp('hasError')

// buildResult :: (String, Boolean) -> a -> Object
const buildResult = (key, isError) =>
  compose(hasError(isError), objOf(key))

// ResultDetail :: { result: Number, hasError: Boolean, error: String }

// createResult :: Result e a -> ResultDetail
const createResult = either(
  buildResult('error', true),
  buildResult('result', false)
)

// doubleNumber :: a -> ResultDetail
const doubleNumber = compose(
  createResult,
  map(double),
  fromNumber
)

doubleNumber(42)
//=> { result: 84, hasError: false }

doubleNumber('value')
//=> { error: 'The value given was not a valid number', hasError: true }

Helper Functions

tryCatch

crocks/Result/tryCatch

tryCatch :: (* -> a) -> * -> Result e a

Used when you want to take any variadic function and wrap it with the added function of a Result type. tryCatch will execute the function with the parameters passed and return either an Ok when successful or anErr when an exception is thrown.

Although we do our best to not use Error to control program flow, there are times when we don't have full control over the behaviour of a function. The saviour in this situation is tryCatch. You can wrap this function and it will always return a Result

import tryCatch from 'crocks/Result/tryCatch'

tryCatch(() => {
  throw new Error('Simple error!')
})()
// Err "Simple error!"

// calculateArea :: (a, b) -> Number
const calculateArea = (width, height) => {
  if (isNaN(width) || isNaN(height)) {
    throw Error('Parameter is not a number!')
  }

  return width * height
}

// tryCalculateArea :: (a, b) -> Result String Number
const tryCalculateArea =
  tryCatch(calculateArea)

tryCalculateArea(3, 6)
//=> Ok 18

tryCalculateArea('String', 5)
// Err "Error: Parameter is not a number!"

// getLength :: a -> Number
const getLength = a =>
  a.length

// tryGetLength :: a -> Result String Number
const tryGetLength =
  tryCatch(getLength)

tryGetLength([ 3, 2, 1 ])
//=> Ok 3

tryGetLength()
//=> Err "TypeError: Cannot read property 'length' of undefined"

Transformation Functions

eitherToResult

crocks/Result/eitherToResult

eitherToResult :: Either b a -> Result b a
eitherToResult :: (a -> Either c b) -> a -> Result c b

Used to transform a given Either instance to a Result instance or flatten a Result of Either into a Result when chained, eitherToMaybe will turn a Right instance into an Ok wrapping the original value contained in the Right. All Left instances will map to an Err, mapping the originally contained value to a Unit. Values on the Left will be lost and as such this transformation is considered lossy in that regard.

Like all crocks transformation functions, eitherToMaybe has two possible signatures and will behave differently when passed either an Either instance or a function that returns an instance of Either. When passed the instance, a transformed Result is returned. When passed an Either returning function, a function will be returned that takes a given value and returns a Result.

import Result from 'crocks/Result'

import Either from 'crocks/Either'
import assign from 'crocks/helpers/assign'
import composeK from 'crocks/helpers/composeK'
import composeB from 'crocks/combinators/composeB'
import eitherToResult from 'crocks/Result/eitherToResult'
import fanout from 'crocks/Pair/fanout'
import isNumber from 'crocks/predicates/isNumber'
import liftA2 from 'crocks/helpers/liftA2'
import map from 'crocks/pointfree/map'
import maybeToEither from 'crocks/Either/maybeToEither'
import merge from 'crocks/pointfree/merge'
import objOf from 'crocks/helpers/objOf'
import getProp from 'crocks/Maybe/getProp'
import safeLift from 'crocks/Maybe/safeLift'

const { Left, Right } = Either
const { Ok } = Result

eitherToResult(Left('no good'))
//=> Err "no good"

eitherToResult(Right('so good'))
//=> Ok "so good"

// safeInc :: a -> Maybe Number
const safeInc =
  safeLift(isNumber, x => x + 1)

// incProp :: String -> a -> Maybe Number
const incProp = key =>
  composeK(safeInc, getProp(key))

// incResult :: String -> a -> Either [ String ] Object
const incResult = key => maybeToEither(
  [ `${key} is not valid` ],
  composeB(
    map(objOf(key)),
    incProp(key)
  )
)

// incThem :: a -> Either [ String ] Object
const incThem = composeB(
  merge(liftA2(assign)),
  fanout(incResult('b'), incResult('a'))
)

Result.of({})
  .chain(eitherToResult(incThem))
//=> Err [ "b is not valid" ]

Result.of({ a: 33 })
  .chain(eitherToResult(incThem))
//=> Err [ "b is not valid" ]

Result.of({ a: 99, b: '41' })
  .chain(eitherToResult(incThem))
//=> Err [ "b is not valid" ]

Result.of({ a: 99, b: 41 })
  .chain(eitherToResult(incThem))
//=> Ok { a: 100, b: 42 }

Ok(Left('Err'))
  .chain(eitherToResult)
//=> Err "Err"

Ok(Right('42'))
  .chain(eitherToResult)
//=> Ok "42"

firstToResult

crocks/Result/firstToResult

firstToResult :: e -> First a -> Result e a
firstToResult :: e -> (a -> First b) -> a -> Result e b

Used to transform a given First instance to a Result instance or flatten a Result of First into a Result when chained, firstToResult will turn a non-empty instance into an Ok wrapping the original value contained within the First. All empty instances will map to an Err with the given value.

Like all crocks transformation functions, firstToResult has two possible signatures and will behave differently when passed either a First instance or a function that returns an instance of First. When passed the instance, a transformed Result is returned. When passed a First returning function, a function will be returned that takes a given value and returns a Result.

import First from 'crocks/First'

import composeB from 'crocks/combinators/composeB'
import concat from 'crocks/pointfree/concat'
import firstToResult from 'crocks/Result/firstToResult'
import flip from 'crocks/combinators/flip'
import mapReduce from 'crocks/helpers/mapReduce'
import getProp from 'crocks/Maybe/getProp'

const { empty } = First

// Person :: { name: String, age: Number }

// createPerson :: (String, Number) -> Person
const createPerson = (name, age) => ({
  name, age
})

// liftName :: Person -> First String
const liftName = composeB(
  First,
  getProp('name')
)

// mergeFirstName :: [ Person ] -> First String
const mergeFirstName = composeB(
  firstToResult('(No name found)'),
  mapReduce(liftName, flip(concat), empty())
)

mergeFirstName([
  createPerson('John', 30),
  createPerson('Jon', 33)
])
//=> Ok "John"

mergeFirstName([
  createPerson(undefined, 30),
  createPerson(undefined, 33)
])
//=> Err "(No name found)"

lastToResult

crocks/Result/lastToResult

lastToResult :: e -> Last a -> Result e a
lastToResult :: e -> (a -> Last b) -> a -> Result e b

Used to transform a given Last instance to a Result instance or flatten a Result of Last into a Result when chained, lastToResult will turn a non-empty instance into a Ok wrapping the original value contained within the Last. All empty instances will map to aErr.

Like all crocks transformation functions, lastToResult has two possible signatures and will behave differently when passed either a Last instance or a function that returns an instance of Last. When passed the instance, a transformed Result is returned. When passed a Last returning function, a function will be returned that takes a given value and returns a Result.

import Result from 'crocks/Result'

import Last from 'crocks/Last'
import lastToResult from 'crocks/Result/lastToResult'

import mconcat from 'crocks/helpers/mconcat'

const { Ok, Err } = Result

// lastValue :: [ a ] -> Last a
const lastValue =
  mconcat(Last)

lastToResult('Error occurred!', Last('the end'))
//=> Ok "the end"

Err('Error occurred!')
  .chain(lastToResult('Error occurred!', lastValue))
//=> Err "Error occurred!"

Ok([])
  .chain(lastToResult('Error occurred!', lastValue))
//=> Err "Error occurred!"

Ok([ 'first', 'second', 'third' ])
  .chain(lastToResult('Error occurred!', lastValue))
//=> Ok "third"

Ok(Last('last'))
  .chain(lastToResult('Error occurred!'))
//=> Ok "last"

maybeToResult

crocks/Result/maybeToResult

maybeToResult :: e -> Maybe a -> Result e a
maybeToResult :: e -> (a -> Maybe b) -> a -> Result e b

Used to transform a given Maybe instance to a Result instance or flatten a Result of Maybe into a Result when chained, maybeToResult will turn a Just instance into an Ok wrapping the original value contained in the Just. All Nothing instances will map to a Err, containing the given e value.

Like all crocks transformation functions, maybeToResult has two possible signatures and will behave differently when passed either a Maybe instance or a function that returns an instance of Maybe. When passed the instance, a transformed Result is returned. When passed a Maybe returning function, a function will be returned that takes a given value and returns a Result. This means that when used with the Maybe-helpers and compose you have a larger collection of Result returning functions.

import Result from 'crocks/Result'

import composeB from 'crocks/combinators/composeB'
import maybeToResult from 'crocks/Result/maybeToResult'
import Maybe from 'crocks/Maybe'
import getProp from 'crocks/Maybe/getProp'

const { Ok } = Result
const { Just, Nothing } = Maybe

maybeToResult('An error occurred', Just('21'))
//=> Ok "21"

maybeToResult('An error occurred', Nothing())
//=> Err "An error occurred"

// Person :: { name: string, age: Number }

// getName :: Person -> Result String
const getName = composeB(
  maybeToResult('Name did not exist or was undefined'),
  getProp('name')
)

getName({ name: 'John', age: 21 })
//=> Ok "John"

getName({ age: 27 })
//=> Err "Name did not exist or was undefined"

Ok(Just('in time!'))
  .chain(maybeToResult('An error occurred'))
//=> Ok "in time!"

Ok(Nothing())
  .chain(maybeToResult('An error occurred'))
//=> Err "An error occurred"

Contribute on Github! Edit this section.