The Either monad
Tags: functional, typescript, fpts
Published: 2022-11-28 01:23:07

The `Either` monad specifies the Either data type as well as several functions that operate on top of it. The `Either` data type represents the result of a computation that may fail. This is how `fp-ts` defines it <sup>[[1]]</sup>:

```ts
interface Left<E> {
  readonly _tag: "Left";
  readonly left: E;
}

interface Right<A> {
  readonly _tag: "Right";
  readonly right: A;
}

export type Either<E, A> = Left<E> | Right<A>;
```

As we can see, it is a simple [tagged union] with two distinct tags: `Left` and `Right`. Left represents the failure while right represents the success of a computation. A computation may fail for different reasons. In such cases, we can use another tagged union to represent the left type, e.g.:

```ts
// parse-int.ts
import type { either } from "fp-ts";

interface NaNError {
  readonly _tag: "NaNError";
  readonly value: number;
}

interface OutOfRangeError {
  readonly _tag: "OutOfRangeError";
  readonly value: number;
}

export type ParseIntError = NaNError | OutOfRangeError;

export type ParseIntResult = either.Either<ParseIntError, number>;

export type ParseInt = (x: string) => ParseIntResult;
export declare const parseInt: ParseInt;
```

In the above example, we just created a new type signature for the (not so?) well known `parseInt` function. The function signature of the original `parseInt` function is the following:

```ts
(string: string, radix?: number | undefined) => number
```

It returns `NaN` in case it cannot parse the string as an integer. It returns `Infinity` in case the parsed number is outside of the safe range, i.e. too big or too small <sup>[[2]]</sup>. The function signature does not indicate these edge cases because both `NaN` and `Infinity` are still typed as `number`. They can only be distinguished from valid numbers at runtime using the [`isNaN`] and [`isFinite`] functions, respectively.

Let's be honest, how many times do we actually remember to handle such edge cases? `parseInt` is not the only example of such functions. There are many that may fail but their type signature does not indicate that: `JSON.parse`, `new Date()`, `new Array()`, `BigInt`, etc.

In my opinion, encoding all possible outcomes of a computation in the type signature is generally better. Life is too short to read possibly nonexistent documentation. Also, there are more important things to keep in mind when our IDEs could help us make sure that these edge cases are handled. For example, by enabling the [`noImplicitReturns`] compiler option:

```ts
import { ParseIntError } from "./parse-int.js";

// @ts-expect-error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
export const handleParseIntError = (err: ParseIntError): number => {
  if (err._tag === "NaNError") {
    return 0;
  }
};
```

Or, by implementing and using an [`assertUnreachable`] function.

Now that we know why `Either` is useful, let's see how to actually use it.

## Constructors

In the [previous post](https://dev.to/attila_vecerek/functor-and-monad-5a5a), we learnt that every monad needs to be constructible, mappable, and chainable. The `Either` monad has two constructors, one for each tag:

```ts
import { either } from "fp-ts";

export const success = either.right(42);
export const failure = either.left({ _tag: "NaNError", value: NaN });
```

## Mapping

The `Either` monad offers two map functions: `map` and `mapLeft`. Each maps the encapsulated value of the corresponding tag. We may map the right/left values to a new value of the same type, or a completely different one, e.g.: turning `Either<Error, number>` into `Either<Error, { count: number }>` or `Either<string, number>`.

For the following code examples, assume we have implemented the `parseInt` function. We can then use `map`/`mapLeft` to evolve the success and failure paths, respectively:

```ts
import { either } from "fp-ts";
import { flow } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

const parseCount = flow(
  parseInt,
  either.map((count) => ({ count })),
  either.mapLeft(({ value }) => `"${value}" could not be parsed as an integer`)
);

export const res = parseCount("42");
// Value of res is { _tag: "Right", right: 42 }

export const res2 = parseCount("abc");
// Value of res2 is { _tag: "Left", left: '"42" could not be parsed as an integer' }
// Type of both res and res2 is Either<string, { count: number }>
```

Without using the Either monad (assume `parseInt` returns `ParseIntError | number` instead), the above code may look like this:

```ts
import { ParseIntError } from "./parse-int.js";

declare const parseInt: (_: string) => ParseIntError | number;

const parseCount = (x: string) => {
  const parsedInt = parseInt(x);

  if (typeof parsedInt === "number") {
    return { count: parsedInt };
  } else {
    return `"${parsedInt.value} could not be parsed as an integer`;
  }
};

export const res = parseCount("42");
// Value of res is 42

export const res2 = parseCount("abc");
// Value of res2 is '"42" could not be parsed as an integer'
// Type of both res and res2 is number | string
```

This code follows more of an imperative programming paradigm. It requires us to:

- store the intermediate result in a variable,
- figure out how to name that variable,
- fork the logic based on the return type.

With the Either monad code, we only need to declare what the different results should map to.

## Chaining

`chain` is kind of similar to `map`. The difference is that the function passed into `chain` must return another `Either`. We can think of it as performing a mapping that may fail.

```ts
import { either } from "fp-ts";
import { pipe } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

const add = (x: string, y: string) =>
  pipe(
    parseInt(x),
    either.chain(
      (a) =>
        pipe(
          parseInt(y),
          either.map((b) => a + b)
        ) // `Either<ParseIntError, number>` is returned by this pipe
    )
  );

export const res = add("42", "24");
// Value of res is { _tag: "Right", right: 66 }

export const res2 = add("abc", "24");
// Value of res2 is { _tag: "Left", left: { _tag: "NaNError", value: NaN } }
// Type of both res and res2 is Either<ParseIntError, number>
```

In the above code, we first parse `"42"` as an integer. Then, we parse `"24"` as an integer but only if the first call to `parseInt` succeeds. Finally, if the second call to `parseInt` succeeds, we add the results together. Should any of the parsing steps fail, the corresponding left result is returned.

Without using the Either monad, the above code may look like this:

```ts
import { ParseIntError } from "./parse-int.js";

declare const parseInt: (_: string) => ParseIntError | number;

const add = (x: string, y: string) => {
  const parsedIntX = parseInt(x);
  const parsedIntY = parseInt(y);

  if (typeof parsedIntX === "number") {
    if (typeof parsedIntY === "number") {
      return parsedIntX + parsedIntY;
    } else {
      return parsedIntY;
    }
  } else {
    return parsedIntX;
  }
};

export const res = add("42", "24");
// Value of res is 66

export const res2 = add("abc", "24");
// Value of res2 is { _tag: "NaNError", value: NaN }
// Type of both res and res2 is number | ParseIntError
```

This code shares the same properties with the imperative code example in the [Mapping section](#mapping). On top of that, it is less readable and maintainable because of the nested `if` statements. Imagine we added three more computations (not necessarily the same `parseInt` steps) each of which could fail. The level of nesting would be three levels deeper making it more likely to introduce bugs. `chain` allows us to flatten otherwise nested if statements like so:

```ts
import { either } from "fp-ts";
import { pipe } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

// either.right represents an arbitrary computation that may fail in this example

pipe(
  parseInt("42"),
  either.chain((x) => either.right(x)),
  either.chain((x) => either.right(x)),
  either.chain((x) => either.right(x)),
  either.chain((x) => either.right(x))
);
```

## Other functions

Besides constructors, mappers, and chainers, monads may implement any additional interfaces. This section describes some of the other functions implemented by the `Either` monad.

### getOrElse

The `map` and `mapLeft` functions, as demonstrated in the [Mapping section](#mapping), produce an instance of an `Either`. What if we wanted to extract its value? That's when we make use of `getOrElse`, which forces us to define a fallback value in case of a failure.

```ts
import { either } from "fp-ts";
import { flow } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

const myParseInt = flow(
  parseInt,
  either.getOrElse(() => 0)
);

export const res = myParseInt("42");
// Value of res is 42, not a Right

export const res2 = myParseInt("abc");
// Value of res2 is 0, not a Left
// Type of both res and res2 is number
```

We can use `getOrElseW` if we want to widen - that's what the W at the end of the function name stands for - the right type of the Either:

```ts
import { either } from "fp-ts";
import { flow } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

const myParseInt = flow(
  parseInt,
  either.getOrElseW(() => "Zero" as const)
);

export const res = myParseInt("42");
// Value of res is 42

export const res2 = myParseInt("abc");
// Value of res2 is "Zero"
// Type of both res and res2 is number | "Zero"
```

In the unlikely scenario that the failure is unrecoverable _and we want our program to crash_, we could also use `getOrElseW` to throw an error:

```ts
import { either } from "fp-ts";
import { flow } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

const throwError = (e: Error) => {
  throw e;
};

const myParseInt = flow(
  parseInt,
  either.getOrElseW(flow(either.toError, throwError))
);

export const res = myParseInt("42");
// Value of res is 42
// Type of res is number

export const res2 = myParseInt("abc");
// res2 never gets assigned a value because an error is thrown
```

[`either.toError`] takes any value and does a best effort conversion to an `Error` object. `throwError` takes an Error object and throws it. Functions that don't return anything and just throw, have a return type of `never`. Hence, `getOrElseW` in this example widens the type of `number` by `never`. However, `number | never` is the same type as `number`.

### fold

`fold` is a function that allows us to transform both tags of an Either at the same time. It expects two functions as the first set of arguments:

1. A function that takes a value of the left type of the Either and returns a value of type `A` (it can be anything).
2. A function that takes a value of the right type of the Either and also returns a value of type `A`.

With an Either value provided as the second argument, `fold` executes one of the two provided functions based on the tag and returns a value of type `A`. It can be used to extract the encapsulated value of the Either as well as perform mappings at the same time:

```ts
import { either } from "fp-ts";
import { flow, pipe } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

const throwError = (e: Error) => {
  throw e;
};

export const res = pipe(
  parseInt("42"),
  either.fold(flow(either.toError, throwError), (count) => ({ count }))
);

// Type of res is { count: number }

// Functionally the same as writing the below code
// but using fewer steps (1 instead of 3):

export const res2 = pipe(
  parseInt("42"),
  either.mapLeft(either.toError),
  either.map((count) => ({ count })),
  either.getOrElseW(throwError)
);
```

It is still possible to throw an error using our `throwError` function from the previous example because any value is assignable to the type of `never`.

### chainFirst

`chainFirst` is similar to `chain`. It takes a function that has to return an `Either`. If that function returns a `Right` value, its result is thrown away. In such case, the result of the previous computation is returned, i.e. the argument passed into the provided function. In the opposite case, the `Left` value is returned. I tend to use it for logging intermediate values or setting breakpoints.

```ts
import { either } from "fp-ts";
import { pipe } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

export const res = pipe(
  parseInt("42"),
  either.chainFirst((count) => {
    console.log({ count });

    return either.right(undefined);
  }),
  either.getOrElse(() => 0)
);

// Type of res is number
```

There's also a `chainFirstW` alternative, which may be useful for fail-fast type of validations that keep widening the Left type with new failure types:

```ts
import { either } from "fp-ts";
import { flow } from "fp-ts/lib/function.js";
import { parseInt } from "./parse-int.js";

interface IncorrectAnswerError {
  readonly _tag: "IncorrectAnswerError";
  readonly value: number;
}

const myParseInt = flow(
  parseInt,
  either.chainFirstW((count) =>
    count === 42
      ? either.right(undefined)
      : either.left({
          _tag: "IncorrectAnswerError",
          value: count,
        } as IncorrectAnswerError)
  )
);

export const res = myParseInt("42");
// Value of res is { _tag: "Right", right: 42 }

export const res2 = myParseInt("abc");
// Value of res2 is { _tag: "Left", left: { _tag: "NaNError", value: NaN } }

export const res3 = myParseInt("41");
// Value of res3 is { _tag: "Left", left: { _tag: "IncorrectAnswerError", value: 41 } }
// Type of res, res2, res3 is the same: Either<ParseIntError | IncorrectAnswerError, number>
```

Assume we were to validate a form with multiple input fields and we wanted to present all the errors at once. We'd have to somehow collect multiple possible failures instead of returning on the first encountered one. In such cases, we should use `either.getValidation`. There's already a [great article](https://dev.to/gcanti/getting-started-with-fp-ts-either-vs-validation-5eja) written about it by the author of `fp-ts` himself. I highly recommend checking it out.

## Wrap-up

- `Either` represents the result of a computation that may fail.
  - `Left` stands for failure.
  - `Right` stands for success.
- We use:
  - `map`: to transform the right value into a different value and this transformation **cannot fail**.
  - `mapLeft`: to transform the left value into a different value and this transformation **cannot fail**.
  - `getOrElse`: to extract the right value while setting a default value in case of a `Left`.
  - `getOrElseW`: to extract the right value while widening its type, or throwing an error on an unrecoverable `Left`.
  - `fold`: to extract a single value from `Either` while applying transformations to both the left and right values.
  - `chain`: to transform the right value into a different value and this transformation **can fail**.
  - `chainFirst`: to log intermediate values or set breakpoints for debugging.
  - `chainFirstW`: to perform one or more fail-fast type of validations.
  - `getValidation`: to perform validations that can be collected.

If I missed a function from the Either monad that you often use or find particularly interesting, or I didn't cover a use case for the functions described in this post, please let me know in the comments 🙏

In the next post of this series, we will look into the `Option` monad.

## Extra resources

- [Getting started with fp-ts: Either vs Validation](https://dev.to/gcanti/getting-started-with-fp-ts-either-vs-validation-5eja)
- [All code samples in this post are available on Github]

[1]: https://github.com/gcanti/fp-ts/blob/9da2137efb295b82fb59b7b0c2114f2ceb40a2b5/src/Either.ts#L112-L134
[2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
[tagged union]: https://en.wikipedia.org/wiki/Tagged_union
[`isNaN`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN
[`isFinite`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite
[`noImplicitReturns`]: https://www.typescriptlang.org/tsconfig#noImplicitReturns
[`assertUnreachable`]: https://stackoverflow.com/a/39419171
[`either.toError`]: https://github.com/gcanti/fp-ts/blob/9da2137efb295b82fb59b7b0c2114f2ceb40a2b5/src/Either.ts#L1401-L1403
[All code samples in this post are available on Github]: https://github.com/vecerek/blog/tree/main/packages/fp-in-ts/src/03-the-either-monad