@algosail/either
v0.1.0
Published
The Either monad — a value that is either a failure (Left) or a success (Right).
Readme
@algosail/either
The Either monad — a value that is either a failure (Left) or a success (Right). Used for explicit error handling without exceptions.
An Either<L, R> is either left(l) (failure/error) or right(r) (success).
Contents
- left
- right
- fromNullable
- fromPredicate
- tryCatch
- encase
- isLeft / isRight
- either
- fromLeft / fromRight / fromEither
- lefts / rights
- mapRight
- mapLeft
- bimap
- ap
- flatmapRight
- flatmapLeft
- flatmapFirst
- alt
- swap
- eitherToMaybe
left
left :: a -> Either a bWraps a value in the Left constructor (failure side).
left('not found') // => { tag: 'left', left: 'not found' }
left(404) // => { tag: 'left', left: 404 }right
right :: b -> Either a bWraps a value in the Right constructor (success side).
right(42) // => { tag: 'right', right: 42 }
right({ id: 1 }) // => { tag: 'right', right: { id: 1 } }fromNullable
fromNullable :: (() -> a) -> b -> Either a bRight when the value is non-null/undefined; Left built from the thunk otherwise.
fromNullable(() => 'missing')(42) // => right(42)
fromNullable(() => 'missing')(null) // => left('missing')
fromNullable(() => 'missing')(undefined) // => left('missing')
fromNullable(() => 'missing')(0) // => right(0)fromPredicate
fromPredicate :: (a -> Boolean, a -> b) -> a -> Either b aRight when the predicate is true; Left via onFalse otherwise.
fromPredicate(
(x) => x > 0,
(x) => `${x} is not positive`,
)(3) // => right(3)
fromPredicate(
(x) => x > 0,
(x) => `${x} is not positive`,
)(-1) // => left('-1 is not positive')tryCatch
tryCatch :: ((...a) -> b, (Error, a) -> c) -> ...a -> Either c bRuns fn; Right on success, Left via onError on throw.
tryCatch(JSON.parse, (e) => e.message)('{"a":1}') // => right({ a: 1 })
tryCatch(JSON.parse, (e) => e.message)('bad') // => left('Unexpected token ...')
// With multiple arguments
const safeDivide = tryCatch(
(a, b) => {
if (b === 0) throw new Error('division by zero')
return a / b
},
(e) => e.message,
)
safeDivide(10, 2) // => right(5)
safeDivide(10, 0) // => left('division by zero')encase
encase :: (a -> b) -> a -> Either Error bLifts a single-argument throwing function into a total Either-returning one. The Left carries the raw Error.
encase(JSON.parse)('{"a":1}') // => right({ a: 1 })
encase(JSON.parse)('bad') // => left(SyntaxError: ...)
encase((x) => x.toUpperCase())('hello') // => right('HELLO')
encase((x) => x.toUpperCase())(null) // => left(TypeError: ...)isLeft / isRight
isLeft :: unknown -> Boolean
isRight :: unknown -> BooleanType guards.
isLeft(left('err')) // => true
isLeft(right(1)) // => false
isRight(right(1)) // => true
isRight(left('err')) // => falseeither
either :: (a -> c) -> (b -> c) -> Either a b -> cCase-fold on Either — eliminates it into a single value.
either((l) => `Error: ${l}`)((r) => r * 2)(right(21)) // => 42
either((l) => `Error: ${l}`)((r) => r * 2)(left('oops')) // => 'Error: oops'fromLeft / fromRight / fromEither
fromLeft :: a -> Either a b -> a
fromRight :: b -> Either a b -> b
fromEither :: Either a a -> aExtract a value with a default for the other side.
fromLeft('default')(left('err')) // => 'err'
fromLeft('default')(right(1)) // => 'default'
fromRight(0)(right(42)) // => 42
fromRight(0)(left('err')) // => 0
fromEither(left(42)) // => 42
fromEither(right(42)) // => 42lefts / rights
lefts :: Array (Either a b) -> Array a
rights :: Array (Either a b) -> Array bPartition an array of Eithers.
const results = [right(1), left('a'), right(2), left('b')]
lefts(results) // => ['a', 'b']
rights(results) // => [1, 2]mapRight
mapRight :: (b -> c) -> Either a b -> Either a cMaps over the Right value, leaving Left untouched.
mapRight((x) => x * 2)(right(5)) // => right(10)
mapRight((x) => x * 2)(left('err')) // => left('err')mapLeft
mapLeft :: (a -> c) -> Either a b -> Either c bMaps over the Left value, leaving Right untouched.
mapLeft((e) => `Wrapped: ${e}`)(left('oops')) // => left('Wrapped: oops')
mapLeft((e) => `Wrapped: ${e}`)(right(42)) // => right(42)bimap
bimap :: (a -> c) -> (b -> d) -> Either a b -> Either c dMaps both sides at once.
bimap((e) => e.toUpperCase())((x) => x + 1)(right(2)) // => right(3)
bimap((e) => e.toUpperCase())((x) => x + 1)(left('err')) // => left('ERR')ap
ap :: Either a (b -> c) -> Either a b -> Either a cApplies a function in a Right to a value in a Right. The first Left encountered short-circuits.
ap(right((x) => x + 1))(right(5)) // => right(6)
ap(left('no fn'))(right(5)) // => left('no fn')
ap(right((x) => x + 1))(left('no v')) // => left('no v')flatmapRight
flatmapRight :: (b -> Either a c) -> Either a b -> Either a cMonadic bind over Right — chains Either-returning functions.
const parseAge = (s) => {
const n = parseInt(s, 10)
return isNaN(n) ? left('not a number') : n < 0 ? left('negative') : right(n)
}
flatmapRight(parseAge)(right('25')) // => right(25)
flatmapRight(parseAge)(right('abc')) // => left('not a number')
flatmapRight(parseAge)(left('prior')) // => left('prior') — short-circuitsflatmapLeft
flatmapLeft :: (a -> Either c b) -> Either a b -> Either c bMonadic bind over Left — allows recovering from failures.
flatmapLeft((e) => right(0))(left('err')) // => right(0) — recovered
flatmapLeft((e) => right(0))(right(42)) // => right(42) — untouchedflatmapFirst
flatmapFirst :: (b -> Either a c) -> Either a b -> Either a bRuns a Right through fn for its Left side effect; keeps the original Right value on success.
// Validate without transforming
const validate = flatmapFirst((x) => (x > 100 ? left('too large') : right(x)))
validate(right(50)) // => right(50) — passes validation
validate(right(200)) // => left('too large')
validate(left('x')) // => left('x') — short-circuitsalt
alt :: Either a b -> Either a b -> Either a bReturns the first Right, or the second argument if both are Left.
alt(right(2))(right(1)) // => right(1) — first Right wins
alt(right(2))(left('x')) // => right(2) — fallback
alt(left('b'))(left('a')) // => left('b') — both Left, returns secondswap
swap :: Either a b -> Either b aSwaps Left and Right.
swap(left(1)) // => right(1)
swap(right(1)) // => left(1)eitherToMaybe
eitherToMaybe :: Either a b -> Maybe bConverts to Maybe — Right becomes Just, Left becomes Nothing.
eitherToMaybe(right(42)) // => just(42)
eitherToMaybe(left('err')) // => nothing()