Skip to content

Either<L, R>

Either is used for computations that may fail. It is either a Left<L> (error) or a Right<R> (success).

1
2
3
4
5
6
7
import { Right } from 'holo-fn/either'

const result = new Right(10)
  .map(n => n * 2)
  .unwrapOr(0)

console.log(result); // 20

Methods

map(fn: (value: R) => U): Either<L, U>

Maps over the Right value. Does nothing for Left.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { Either, Left, Right } from "holo-fn/either";

const calculate = (a: number, b: number): Either<string, number> => {
  if (b === 0) {
    return new Left("Division by zero");
  }

  return new Right(a / b);
};

const result1 = calculate(10, 2)
  .map(n => n * 2)
  .unwrapOr(0);

console.log(result1); // 10

const result2 = calculate(10, 0)
  .map(n => n * 2)
  .unwrapOr(0);

console.log(result2); // 0

mapLeft<M>(fn: (err: L) => M): Either<M, R>

Maps over the Left value. Does nothing for Right.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Either, Left, Right } from "holo-fn/either";

const calculate = (a: number, b: number): Either<string, number> => {
  if (b === 0) {
    return new Left("Division by zero");
  }

  return new Right(a / b);
};

const result1 = calculate(10, 2)
  .map(n => n * 2)
  .mapLeft(e => console.log(`Error: ${e}`)) // No printing here
  .unwrapOr(0);

console.log(result1); // 10

const result2 = calculate(10, 0)
  .map(n => n * 2)
  .mapLeft(e => console.log(`Error: ${e}`)) // Prints "Error: Division by zero"
  .unwrapOr(0);

console.log(result2); // 0

chain(fn: (value: R) => Either<L, U>): Either<L, U>

Chains the transformation if the value is Right. Returns Left otherwise.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Either, Left, Right } from "holo-fn/either";

const calculate = (a: number, b: number): Either<string, number> => {
  if (b === 0) {
    return new Left("Division by zero");
  }

  return new Right(a / b);
};

const result1 = calculate(12, 2)
  .chain(n => n > 5 ? new Right(n * 2) : new Left("Result is too small"))
  .map(n => n + 1)
  .mapLeft(e => console.log(`Error: ${e}`)) // Not run
  .unwrapOr(0);


console.log(result1); // 13

const result2 = calculate(10, 2)
  .chain(n => n > 5 ? new Right(n * 2) : new Left("Result is too small"))
  .map(n => n + 1)
  .mapLeft(e => console.log(`Error: ${e}`)) // Prints "Error: Result is too small"
  .unwrapOr(0);

console.log(result2); // 0

unwrapOr(defaultValue: R): R

Returns the value of Right, or the default value for Left.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { Either, Left, Right } from "holo-fn/either";

const calculate = (a: number, b: number): Either<string, number> => {
  if (b === 0) {
    return new Left("Division by zero");
  }

  return new Right(a / b);
};

const result1 = calculate(12, 2).unwrapOr(0);
console.log(result1); // 6

const result2 = calculate(10, 0).unwrapOr(-1);
console.log(result2); // -1

isRight(): boolean

Checks if the value is Right.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { Either, Left, Right } from "holo-fn/either";

const calculate = (a: number, b: number): Either<string, number> => {
  if (b === 0) {
    return new Left("Division by zero");
  }

  return new Right(a / b);
};

const result1 = calculate(12, 2).isRight();
console.log(result1); // true

const result2 = calculate(10, 0).isRight();
console.log(result2); // false

isLeft(): boolean

Checks if the value is Left.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { Either, Left, Right } from "holo-fn/either";

const calculate = (a: number, b: number): Either<string, number> => {
  if (b === 0) {
    return new Left("Division by zero");
  }

  return new Right(a / b);
};

const result1 = calculate(12, 2).isLeft();
console.log(result1); // false

const result2 = calculate(10, 0).isLeft();
console.log(result2); // true

match<T>(cases: { left: (left: L) => T; right: (right: R) => T }): T

Matches the value to execute either the left or right case.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { Either, Left, Right } from "holo-fn/either";

const calculate = (a: number, b: number): Either<string, number> => {
  if (b === 0) {
    return new Left("Division by zero");
  }

  return new Right(a / b);
};

const result1 = calculate(12, 2)
  .chain(n => n > 5 ? new Right(n * 2) : new Left("Result is too small"))
  .map(n => n + 1)
  .match({
    right: n => n,
    left: e => {
      console.log(`Error: ${e}`); // Not run
      return 0;
    }
  });

console.log(result1); // 13

const result2 = calculate(10, 2)
  .chain(n => n > 5 ? new Right(n * 2) : new Left("Result is too small"))
  .map(n => n + 1)
  .match({
    right: n => n,
    left: e => {
      console.log(`Error: ${e}`); // Prints "Error: Result is too small"
      return 0;
    }
  });

console.log(result2); // 0

equals(other: Either<L, R>): boolean

Compares this to another Either, returns false if the values inside are different.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { Either, Left, Right } from "holo-fn/either";

const calculate = (a: number, b: number): Either<string, number> => {
  if (b === 0) {
    return new Left("Division by zero");
  }

  return new Right(a / b);
};

const result1 = calculate(12, 2)
  .chain(n => n > 5 ? new Right(n * 2) : new Left("Result is too small"))
  .map(n => n + 1);

console.log(result1.equals(new Right(13))); // true

const result2 = calculate(10, 2)
  .chain(n => n > 5 ? new Right(n * 2) : new Left("Result is too small"))
  .map(n => n + 1);

console.log(result2.equals(new Right(0))); // false

Helpers

tryCatch(fn, onError?)

Wraps a potentially throwing function in an Either.

1
2
3
4
5
6
7
8
9
import { tryCatch } from 'holo-fn/either'

const input = '{"user": "John Doe"}'

const parsed = tryCatch(() => JSON.parse(input), e => 'Invalid JSON')
  .map(obj => obj.user)
  .unwrapOr('anonymous')

console.log(parsed) // John Doe
  • Returns Right<R> if fn() succeeds
  • Returns Left<L> if it throws, using onError if provided

fromPromise(promise, onError?)

Wraps a Promise<T> into a Promise<Either<L, R>>.

1
2
3
4
5
import { fromPromise } from 'holo-fn/either'

const result = await fromPromise(fetch('/api'), e => 'Network error')

console.log(result) // _Left { value: 'Network error' }
  • Resolves to Right<R> on success
  • Resolves to Left<L> on failure

fromAsync(fn, onError?)

Same as fromPromise, but lazy — receives a function returning a Promise.

1
2
3
4
5
import { fromAsync } from 'holo-fn/either'

const result = await fromAsync(async () => await fetch('/api'), e => 'Request failed')

console.log(result) // _Left { value: 'Request failed' }
  • Allows deferred execution
  • Handles exceptions from async () => ...

Curried Helpers

mapE

Curried version of map for Either. This allows functional composition with pipe.

1
2
3
4
5
6
7
8
9
import { mapE, Right } from 'holo-fn/either';

const result = pipe(
  new Right(5),
  mapE((x) => x * 2),
  (res) => res.unwrapOr(0)
);

console.log(result); // 10

mapLeftE

Curried version of mapLeft for Either. This allows mapping over the Left value in a functional pipeline.

1
2
3
4
5
6
7
8
9
import { Left, mapLeftE } from 'holo-fn/either';

const result = pipe(
  new Left("Error"),
  mapLeftE((e) => `Mapped error: ${e}`),
  (res) => res.unwrapOr("No value") 
);

console.log(result); // "No value"

chainE

Curried version of chain for Either. This allows chaining transformations on the Right value of Either, using a functional composition style.

1
2
3
4
5
6
7
8
9
import { Right, chainE } from 'holo-fn/either';

const result = pipe(
  new Right(5),
  chainE((x) => new Right(x + 5)),
  (res) => res.unwrapOr(0)
);

console.log(result); // 10

unwrapOrE

Curried version of unwrapOr for Either. This provides a cleaner way to unwrap the value in a Either, returning a default value if it's Left.

1
2
3
4
5
6
7
8
import { Left, unwrapOrE } from 'holo-fn/either';

const result = pipe(
  new Left("Fail"),
  unwrapOrE<string, unknown>("No value")
);

console.log(result); // "No value"

matchE

Curried version of match for Either. This allows handling Left and Right in a functional way.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { matchE, Right } from 'holo-fn/either';

const result = pipe(
  new Right(10),
  matchE({
    left: (e) => `Error: ${e}`,
    right: (v) => `Success: ${v}`
  })
);

console.log(result); // "Success: 10"

equalsE

Curried version of equals for Either. Compares this to another Either, returns false if the values inside are different.

1
2
3
4
5
6
7
8
import { equalsE, Right } from 'holo-fn/either';

const result = pipe(
  new Right(10),
  equalsE(new Right(10))
);

console.log(result); // true