Skip to content

Either ​

ts
type Either<E, B> = Left<E> | Right<B>

Either is a data type with two sides (Left and Right). It is most commonly used for error handling similar as Maybe.

It is also important to note that Either is right-biased which means that map, chain and other similar methods will operate on the right side.

Example ​

Without Either

ts
function parseJson(s: string): never | Record<string, unknown> {
  try {
    return JSON.parse(s)
  } catch (e) {
    throw new Error(`Couldn't parse from ${s}.`)
  }
}

let parsed: Record<string, unknown>

// try to parse some content that may not be parsed
try {
  parsed = parseJson('some content')
} catch(e) {
  parsed = { message: 'err' }
}

With Either

ts
import { tryCatch, getOrElse } from 'sa-lambda/either'
import { pipe } from 'sa-lambda'

const parseJson = (s: string) =>
  tryCatch(() => JSON.parse(s), () => `Couldn't parse from ${s}.`)

let parsed = pipe('some content', parseJson, getOrElse(() => ({ message: 'err' })))

API ​

left ​

ts
<E, A = never>(e: E) => Either<E, A>

Constructs a new Either holding a Left value. Represents a failure value.

ts
left(1) ➔ { readonly _tag: 'Left', readonly left: 1 }
ts
<A, E = never>(a: A) => Either<E, A>

Constructs a new Either holding a Right value. Represents a successful value.

ts
right(1) ➔ { readonly _tag: 'Right', readonly right: 1 }

isLeft ​

ts
<E>(ma: Either<E, unknown>) => ma is Left<E>

Returns whether the Either is Left or not.

ts
isLeft(left(1))  ➔ true
isLeft(right(1)) ➔ false

isRight ​

ts
<A>(ma: Either<unknown, A>) => ma is Right<A>

Returns whether the Either is Right or not.

ts
isRight(left(1))  ➔ false
isRight(right(1)) ➔ true

of ​

ts
<A, E = never>(a: A) => Either<E, A>

Takes a value and wraps it into a Right.

ts
of(1) ➔ right(1)

zero ​

ts
<E, A = never>(e: E) => Either<E, A>

Alias of left

empty ​

ts
<E, A = never>(e: E) => Either<E, A>

Alias of left

map ​

ts
<A, B>(f: (a: A) => B) => <E>(ma: Either<E, A>) => Left<E> | Right<B>

Maps the Right value.

ts
pipe(left(1), map((n: number) => n + 1))  ➔ left(1)
pipe(right(1), map((n: number) => n + 1)) ➔ right(2)

alt ​

ts
<E2, B>(that: Lazy<Either<E2, B>>) => <E1, A>(ma: Either<E1, A>) => Either<E2, B | A>

Returns the Either if it is Right, otherwise returns the function result.

ts
pipe(right(2), alt(() => right(1))) ➔ right(2)
pipe(left(2), alt(() => right(1)))  ➔ right(1)
pipe(right(2), alt(() => left(1)))  ➔ right(2)
pipe(left(2), alt(() => left(1)))   ➔ left(1)

ap ​

ts
<E2, A>(ma: Either<E2, A>) => <E1, B>(fab: Either<E1, (a: A) => B>) => Either<E2 | E1, B>

Applies a Right function over a Right value. Returns Left if the Either or the function is Left.

ts
pipe(left(1), ap(left(0)))                      ➔ left(1)
pipe(left(1), ap(right(0)))                     ➔ left(1)
pipe(right((n: number) => n + 1)), ap(left(0))  ➔ left(0)
pipe(right((n: number) => n + 1)), ap(right(0)) ➔ right(1)

match ​

ts
<E, B, A, C>(onLeft: (e: E) => B, onRight: (a: A) => C) => (ma: Either<E, A>) => B | C

Takes two functions and an Either value, if the value is Left, returns the onLeft function result, if the value is Right, returns the onRight function result.

ts
pipe(left(1), match((n: number) => n -1), (n: number) => n + 1)  ➔ 0
pipe(right(1), match((n: number) => n -1), (n: number) => n + 1) ➔ 2

extend ​

ts
<E, A, B>(f: (ma: Either<E, A>) => B) => (ma: Either<E, A>) => Left<E> | Right<B>

Returns the Either if it's a Left, otherwise returns the result of the applying function and wrapped in a Right.

ts
pipe(right(1), extend(() => 2)) ➔ right(2)
pipe(left(1), extend(() => 2))  ➔ left(1)

chain ​

ts
<E2, A, B>(f: (a: A) => Either<E2, B>) => <E1>(ma: Either<E1, A>) => Either<E2 | E1, B>

Composes computations in sequence. Useful for chaining many computations that may fail.

ts
pipe(left(1), chain((n: number) => right(n + 1)))  ➔ left(1)
pipe(right(1), chain((n: number) => right(n + 1))) ➔ right(2)

chainRec ​

ts
<E, A, B>(f: (a: A) => Either<E, Either<A, B>>) => (ma: A) => Either<E, B>

Chains recursively until the next is Right.

ts
pipe(right(1), chainRec( a => a < 5 ? left(a + 1) : right(`${a}`))) ➔ right('5')

reduce ​

ts
<E, A, B>(f: (acc: B, a: A) => B, b: B) => (ma: Either<E, A>) => B

Takes a function and an initial value and returns the initial value if Either is Left, otherwise returns the result of applying the function to the initial value and the value inside Either.

ts
pipe(right(1), reduce((acc, a) => acc + a, 1)) ➔ 2
pipe(left(0), reduce((acc, a) => acc + a, 1))  ➔ 1

traverse ​

ts
PipeableTraverse2<EitherKind>

Maps each element of a HKT structure to an action, and collects the results wrapped in Right.

Returns a HKT contains a left with the value of Either if the Either is a Left.

ts
const f = traverse(Maybe.Monad)((n: number) => n > 0 ? some(n): none
pipe(left('err'), f) ➔ some(left('err'))
pipe(right(1), f)    ➔ some(right(1))
pipe(right(-1), f)   ➔ none

swap ​

ts
<E, A>(ma: Either<E, A>) => Either<A, E>

Returns Right if Either is Left and vice versa.

ts
swap(right(1)) ➔ left(1)
swap(left(1))  ➔ right(1)

equals ​

ts
<E, A>(a: Either<E, A>, b: Either<E, A>) => boolean

Compares one Either to another Either. Returns false if eithers or the wrapped values are different.

ts
equals(right(1), right(1)) ➔ true
equals(right(1), left(1))  ➔ false
equals(left(1), left(1))   ➔ true
equals(left(1), right(1))  ➔ false

exists ​

ts
<A>(predicate: Predicate<A>) => <E>(ma: Either<E, A>) => boolean

Returns false if Either is a Left, otherwise returns the predicate result.

ts
pipe(left(0), exists((n: number) => n > 0))  ➔ false
pipe(right(0), exists((n: number) => n > 0)) ➔ false
pipe(left(1), exists((n: number) => n > 0))  ➔ false
pipe(right(1), exists((n: number) => n > 0)) ➔ true

tryCatch ​

ts
<E, A>(f: Lazy<A>, onThrow: (e: unknown) => E) => Either<E, A>

Returns a Either from a function that might throw.

ts
const unsafeDiv = (top: number, bottom: number) => {
  if (bottom === 0) throw new Error('unsafe division')

  return top / bottom
}

const div = (top: number, bottom: number) => tryCatch(() => unsafeDiv(top, bottom), () => 0)

div(2, 0) ➔ left(0)
div(2, 1) ➔ right(2)

orElse ​

ts
<E1, E2, B>(onLeft: (e: E1) => Either<E2, B>) => <A>(ma: Either<E1, A>) => Either<E2, B | A>

Returns Either if it's a Right, otherwise returns onLeft result.

ts
pipe(left(1), orElse((n: number) => right(n + 1)))  ➔ right(2)
pipe(right(1), orElse((n: number) => right(n + 1))) ➔ right(1)

extract ​

ts
<E, A>(ma: Either<E, A>) => E | A

Extracts the value out of Either.

ts
extract(right(1))    ➔ 1
extract(left('err')) ➔ 'err'

getLeft ​

ts
<E, A>(ma: Either<E, A>) => Maybe<E>

Returns the Left value of an Either if possible.

ts
pipe(right(1), getLeft) ➔ none
pipe(left(1), getLeft)  ➔ some(1)

getRight ​

ts
<E, A>(ma: Either<E, A>) => Maybe<A>

Returns the Right value of an Either if possible.

ts
pipe(right(1), getRight) ➔ some(1)
pipe(left(1), getRight)  ➔ none

getOrElse ​

ts
<E, B>(onLeft: (e: E) => B) => <A>(ma: Either<E, A>) => B | A

Returns the Either value if it's a Right or a default onLeft result value if it's a Left.

ts
pipe(left(1), getOrElse(() => 0))  ➔ 0
pipe(right(1), getOrElse(() => 0)) ➔ 1

filterOrElse ​

ts
<A, E2>(predicate: Predicate<A>, onFalse: (a: A) => E2) => <E1, B extends A>(ma: Either<E1, B>) => Left<E1> | Right<B> | Left<E2>

Returns Either if it is a Left or the result of predicate is true, otherwise returns the result of applying onFalse function to value inside Either and wrapped in a Left.

ts
const f = filterOrElse(
  (n: number) => n > 0,
  () => 'err'
)
pipe(right(1), f)  ➔ right(1)
pipe(right(-1), f) ➔ left('err')
pipe(left(1), f)   ➔ left(1)

fromPredicate ​

ts
<A, E>(predicate: Predicate<A>, onFalse: Lazy<E>): (a: A) => Either<E, A>

Constructs a Left or Right based on the given predicate.

ts
pipe(1, fromPredicate((n: number) => n > 0, () => 'error')) ➔ right(1)
pipe(0, fromPredicate((n: number) => n > 0, () => 'error')) ➔ left('error')

fromMaybe ​

ts
<E>(onNone: Lazy<E>) => <A>(ma: Maybe<A>) => Left<E> | Right<A>

Returns Left or Right based on the given Maybe.

ts
pipe(some(1), fromMaybe(() => 'error')) ➔ right(1)
pipe(none, fromMaybe(() => 'error'))    ➔ left('error')

Released under the MIT License.