Either
The Either
type represents values that can be one of two types: a Left
containing an error value, or a Right
containing a success value.
It is commonly used for error handling and expressing computations that may fail. Instead of throwing exceptions, functions can return an Either
that explicitly represents both success and failure cases.
Throwing exceptions complicates control flow and hides exceptions from the type system, making it harder to reason about error handling in your code.
Examples
Section titled “Examples”Functions that can fail with exceptions can become hard to compose and manage. Exception handling adds complexity and makes control flow harder to follow.
import { expect } from "jsr:@std/expect"
const divide = (a: number, b: number): number => { if (b === 0) { throw new Error("Division by zero") } return a / b}
const increment = (a: number): number => a + 1
try { expect(increment(divide(6, 2))).toEqual(4) expect(increment(divide(6, 0))) // Throws error} catch (e) { // handle error}
Using Either
makes error handling explicit and composable. The error cases are handled through the type system rather than exceptions.
import { expect } from "jsr:@std/expect"import { Either, pipe } from "@jvlk/fp-tsm"
const divide = (a: number, b: number): Either.Either<string, number> => b === 0 ? Either.left("Division by zero") : Either.right(a / b)
const increment = (a: Either.Either<string, number>): Either.Either<string, number> => pipe( a, Either.map(x => x + 1) )
expect(increment(divide(6, 2))).toEqual(Either.right(4))expect(increment(divide(6, 0))).toEqual(Either.left("Division by zero"))
Creating Eithers
Section titled “Creating Eithers”Constructs a Right
. Represents a successful value in an Either.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
// when creating an Either using `left` or `right` you should provide the type parametersexpect(Either.right(1)).toEqual({ _tag: "Right", right: 1 })expect(Either.right("hello")).toEqual({ _tag: "Right", right: "hello" })
Constructs a Left
. Represents a failure value in an Either.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
// when creating an Either using `left` or `right` you should provide the type parametersexpect(Either.left("error")).toEqual({ _tag: "Left", left: "error" })expect(Either.left(404)).toEqual({ _tag: "Left", left: 404 })
tryCatch
Section titled “tryCatch”tryCatch
is a utility function that allows you to execute a function that may throw an unknown
error and return an Either
.
It can take an optional second argument, catchFn
, which is a function that transforms the unknown
error into something else.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
expect(Either.tryCatch(() => 1)).toEqual(Either.right(1))
expect(Either.tryCatch(() => { throw new Error("Error") })) .toEqual(Either.left(Error("Error")))
expect(Either.tryCatch(() => { throw new Error("something went wrong") }, (e) => `Caught: ${e}`)).toEqual(Either.left("Caught: Error: something went wrong"))
fromPredicate
Section titled “fromPredicate”You can create an Either
based on a predicate, for example, to check if a value is positive.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
const isPositive = Either.fromPredicate( (n: number): n is number => n >= 0, () => "Number must be positive")
expect(isPositive(-1)).toEqual(Either.left("Number must be positive"))expect(isPositive(1)).toEqual(Either.right(1))
Working with Eithers
Section titled “Working with Eithers”The Either.map
function lets you transform the value inside an Either
without manually unwrapping and re-wrapping it.
If the Either
holds a right value (Right
), the transformation function is applied.
If the Either
is Left
, the function is ignored, and the Left
value remains unchanged.
Examples
Section titled “Examples”Mapping a Value in Right
import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
// Transform the value inside Rightexpect(Either.map(Either.right(1), (n: number) => n + 1)).toEqual(Either.right(2))
Mapping over Left
import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
// Mapping over Left results in the same Leftexpect(Either.map(Either.left("error"), (n: number) => n + 1)).toEqual(Either.left("error"))
mapLeft
Section titled “mapLeft”The Either.mapLeft
function lets you transform the error value inside an Either
without manually unwrapping and re-wrapping it.
If the Either
holds a left value (Left
), the transformation function is applied.
If the Either
is Right
, the function is ignored, and the Right
value remains unchanged.
Examples
Section titled “Examples”Mapping an Error Value in Left
import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
// Transform the error value inside Leftexpect(Either.mapLeft(Either.left("error"), (s: string) => s.toUpperCase())) .toEqual(Either.left("ERROR"))
Mapping Left over Right
import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
// Mapping Left over Right results in the same Rightexpect(Either.mapLeft(Either.right(1), (s: string) => s.toUpperCase())) .toEqual(Either.right(1))
Maps over both parts of an Either simultaneously using two functions. If the Either is Left, applies the first function; if Right, applies the second function.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either, pipe } from "@jvlk/fp-tsm"
expect(Either.bimap(Either.right(1), (e: string) => e.toUpperCase(), (n: number) => n + 1)).toEqual(Either.right(2))
expect(Either.bimap(Either.left("error"), (e: string) => e.toUpperCase(), (n: number) => n + 1)).toEqual(Either.left("ERROR"))
expect( pipe( Either.right(1), Either.bimap( (e: string) => e.toUpperCase(), (n: number) => n + 1 ) )).toEqual(Either.right(2))
flatMap
Section titled “flatMap”Applies a function to the value of a Right
and flattens the resulting
Either
. If the input is Left
, it remains Left
.
This function allows you to chain computations that return Either
values.
If the input Either
is Right
, the provided function f
is applied to the
contained value, and the resulting Either
is returned. If the input is
Left
, the function is not applied, and the result remains Left
.
This utility is particularly useful for sequencing operations that may fail, enabling clean and concise workflows for handling error cases.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either, pipe } from "@jvlk/fp-tsm"
type Error = stringtype User = { readonly id: number readonly address: Either.Either<Error, string>}
const user: User = { id: 1, address: Either.right("123 Main St")}
const validateAddress = (address: string): Either.Either<Error, string> => address.length > 0 ? Either.right(address) : Either.left("Invalid address")
const result = pipe( user.address, Either.flatMap(validateAddress))
expect(result).toEqual(Either.right("123 Main St"))
flatMapLeft
Section titled “flatMapLeft”Applies a function to the value of a Left
and flattens the resulting
Either
. If the input is Right
, it remains Right
.
This function is the left-sided equivalent of flatMap
. It allows you to chain
computations on the Left
value while preserving any Right
value unchanged.
Examples
Section titled “Examples”import { Either, pipe } from "@jvlk/fp-tsm"
const result = pipe( Either.left("error"), Either.flatMapLeft(error => Either.left(error.toUpperCase())))// Result: Either.left("ERROR")
Matches an Either
against two functions: one for the Left
case and one for the Right
case.
This is useful for handling both cases in a single expression without needing to check the _tag
manually.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either, pipe } from "@jvlk/fp-tsm"
expect(Either.match(Either.right(42), e => `Error: ${e}`, val => `The value is ${val}.`)).toEqual("The value is 42.")expect(Either.match(Either.left("error"), e => `Error: ${e}`, val => `The value is ${val}.`)).toEqual("Error: error")
expect( pipe( Either.right(42), Either.match( e => `Error: ${e}`, val => `The value is ${val}.` ) )).toEqual("The value is 42.")
expect( pipe( Either.left("error"), Either.match( e => `Error: ${e}`, val => `The value is ${val}.` ) )).toEqual("Error: error")
getOrElse
Section titled “getOrElse”Returns the value inside a Right
, or the result of onLeft
if the Either
is a Left
.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either, pipe } from "@jvlk/fp-tsm"
expect(Either.getOrElse(Either.right(1), () => 0)).toEqual(1)expect(Either.getOrElse(Either.left("error"), () => 0)).toEqual(0)
expect( pipe( Either.right(42), Either.getOrElse(() => 10), )).toEqual(42)
expect( pipe( Either.left("error"), Either.getOrElse(() => 10), )).toEqual(10)
Type Guards
Section titled “Type Guards”isRight
Section titled “isRight”Checks if an Either
is a Right
value. This works as a valid type guard, allowing TypeScript to narrow the type of the Either
to Right<R>
when this function returns true
.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
expect(Either.isRight(Either.right(1))).toEqual(true)expect(Either.isRight(Either.left("error"))).toEqual(false)
isLeft
Section titled “isLeft”Checks if an Either
is a Left
value. This works as a valid type guard, allowing TypeScript to narrow the type of the Either
to Left<L>
when this function returns true
.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
expect(Either.isLeft(Either.right(1))).toEqual(false)expect(Either.isLeft(Either.left("error"))).toEqual(true)
Conversion
Section titled “Conversion”fromOption
Section titled “fromOption”Converts an Option
to an Either
. If the Option
is Some
, it returns Right
with the contained value; if it is None
, it returns Left
with the provided error value.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either, Option } from "@jvlk/fp-tsm"
expect(Either.fromOption(Option.some(1), () => "error")).toEqual(Either.right(1))expect(Either.fromOption(Option.none, () => "error")).toEqual(Either.left("error"))
Working with multiple Eithers
Section titled “Working with multiple Eithers”Do notation allows you to yield Either
values and combine them in a sequential manner without having to manually check for Left
at each step.
The yield*
operator is used to work with multiple Either
values in a generator function. Each value must be yielded with Either.bind()
.
Examples
Section titled “Examples”import { expect } from "jsr:@std/expect"import { Either } from "@jvlk/fp-tsm"
const age = Either.right(30)const name = Either.right("John")const city = Either.right("New York")
const data = Either.Do(function* () { const personAge = yield* Either.bind(age) const personName = yield* Either.bind(name) const personCity = yield* Either.bind(city) return `Hello ${personName}! You are ${personAge} years old and live in ${personCity}.`})
expect(data).toEqual(Either.right("Hello John! You are 30 years old and live in New York."))
// If any Either is Left, the entire result is Leftconst data2 = Either.Do(function* () { const personAge = yield* Either.bind(Either.left("Error")) const personName = yield* Either.bind(name) return `Hello ${personName}! You are ${personAge} years old.`})
expect(data2).toEqual(Either.left("Error"))
Without Do notation, the same code would be much more verbose.
import { expect } from "jsr:@std/expect"import { Either, pipe } from "@jvlk/fp-tsm"
const age = Either.right(30)const name = Either.right("John")const city = Either.right("New York")
const result = pipe( age, Either.flatMap(personAge => pipe( name, Either.flatMap(personName => pipe( city, Either.map(personCity => `Hello ${personName}! You are ${personAge} years old and live in ${personCity}.` ) ) ) ) ))
expect(result).toEqual(Either.right("Hello John! You are 30 years old and live in New York."))