Safer code with container types (Either and Maybe)
Tags: typescript
Published: 2020-01-06 15:34:18

---
title: Safer code with container types (Either and Maybe)
published: true 
date: 2019-12-26 17:00:26 UTC
tags: typescript
canonical_url: https://blog.logrocket.com/safer-code-with-container-types-either-and-maybe/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/n1mu00cnsbwgp6h3llk7.png
---
**Written by [John Llenas](https://blog.logrocket.com/author/joan-llenas/)**✏️

### The Maybe data type

There are only two hard things in Computer Science: `null` and `undefined`.

Well, [Phil Karlton](https://martinfowler.com/bliki/TwoHardThings.html) didn’t say exactly those words, but we can all agree that dealing with `null`, `undefined`, and the concept of emptiness in general is hard, right?

### The absence of a value

Every time we annotate a variable with a Type, that variable can hold either a value of the annotated Type, `null`, or `undefined` (and sometimes even `NaN`!).

That means, to avoid errors like `Cannot read property 'XYZ' of undefined`, we must remember to consistently apply defensive programming techniques every time we use it.

Another tricky aspect of the above fact is that semantically, it’s very confusing what `null` and `undefined` should be used for. They can mean different things for different people. APIs also use them inconsistently.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

### What can go wrong?

Even if you apply defensive programming techniques, things can go wrong. There are all sort of ways in which you could end up with false negatives.

In this example, the number `0` will never be found because `0` is `_falsy_`, like `undefined`. The `Array.find()` result when the find operation doesn’t match anything.

```jsx
const numToFind = 0;
const theNum = [0, 1, 2, 3].find(n => n === numToFind);
if (theNum) {
  console.log(`${theNum} was found`);
} else {
  console.log(`${theNum} was not found`);
}
```

Sadly, defensive programming (aka `undefined` / `null` checks behind `if` statements) are also a common source of bugs.

### `Maybe` to the rescue

Wouldn’t it be nice if we could consistently handle emptiness, with the help of the compiler and without false negatives?

There’s already something that does all that: it’s the `Maybe` data type (also known as `Option`).

`Maybe` encapsulates the idea of a value that might not be there.

A `Maybe` value can either be `Just` some value or `Nothing`.

```jsx
type Maybe<T> = Just<T> | Nothing;
```

We often talk about this kind of Types as `_Container Types_`, because their only purpose is to give semantic meaning to the value they hold and to allow you to perform specific operations on it in a safe way.

We are going to use the [ts.data.maybe](https://github.com/joanllenas/ts.data.maybe) Library. Let’s get familiar with its API.

Our app has a `User` Type:

```jsx
interface User {
  id: number;
  nickname: string;
  email: string;
  bio: string;
  dob: string;
}
```

We also make a `/users` request to our REST API, which returns the following payload:

```jsx
[
  (...)
  {
    "id": 1234,
    "nickname": "picklerick",
    "email": "pickle@rick.com",
    "bio": null
  }
  (...)
]
```

At this point, if we annotate this payload with `User[]`, lots of bad things can happen in our codebase because `User` is lying. We are expecting `bio` and `dob` to always be a `string`, but in this case, one is `null` and the other is `undefined`.

There’s a potential for runtime errors.

### Type annotations with `Maybe`

Let’s fix this with `Maybe`.

```jsx
import { Maybe } from "ts.data.maybe";

interface User {
  id: number;
  nickname: string;
  email: string;
  bio: Maybe<string>;
  dob: Maybe<Date>;
}
```

Once you add `Maybe` to the equation, nothing is implicit anymore. There’s no way to get around that Type declaration — the compiler will always force you to treat `bio` and `dob` as `Maybe`.

### Creating `Maybe` instances

Ok, but how do we use this?

Let’s create a `User` parser for our API result.

For this, we’ll create a `UserJson` Type, used only by the parser, that represents what we are getting from the server and another `User` Type that represents our domain model. We’ll use this Type throughout the application.

```jsx
import { Maybe, just, nothing } from 'ts.data.maybe';

interface User {
  id: number;
  nickname: string;
  email: string;
  bio: Maybe<string>;
  dob: Maybe<Date>;
}

interface UserJson {
  id: number;
  nickname: string;
  email: string;
  bio?: string | null;
  dob?: string | null;
}
```

As you can see, to create a `Maybe` instance, you have to use one of the available constructor functions:  
– `just<T>(value: T): Maybe<T>`  
– `nothing<T>(): Maybe<T>`

Notice how we’ve decided to define emptiness differently for `bio` and `dob` (date of birth).

We can represent `bio` with an empty string — there’s nothing wrong with that.

However, we cannot represent a `Date` with an empty string. That’s why the parser treats them differently, even though the data that comes from the server is a `string` for both.

### Extracting values from `Maybe`

Now that we’ve managed to declare and create `Maybe` instances, let’s see how we can use them in our logic.

We plan to create an html representation of the list of users that we are getting from the server, and we are going to represent them with cards.

This is the function that we’ll use to generate the Html markup for the card:

```jsx
const userCard = (user: User) => `<div class="card">
  <h2>${user.nickname}</h2>
  <p>${userBio(user.bio)}</p>
  <ul>
    <li>${user.email}</li>
    <li>${userDob(user.dob)}</li>
  </ul>
</div>`;
```

Nothing special so far, but let’s see what those two functions that extract the values from `Maybe` look like:

### `userBio()`

```jsx
const userBio = (maybeBio: Maybe<string>) =>
  withDefault(maybeBio, '404 bio not found');
```

Here, we have introduced a new `Maybe` API: the `withDefault()` function (also known as `getOrElse()` in other `Maybe` implementations).

```jsx
withDefault<A>(value: Maybe<A>, defaultValue: A): A
```

This function is used to extract the value from a `Maybe` instance.

If the `Maybe` instance is `Nothing`, then the default value will be returned — in this case, `404 bio not found`. If the instance is a `Just`, it will unwrap and return the string value it contains.

i.e.  
`withDefault(just(5), 0)` would return `5`.  
`withDefault(nothing(), 'This is empty')` would return `This is empty`.

### `userDob()`

```jsx
const userDob = (maybeDate: Maybe<Date>) =>
  caseOf(
    {
      Nothing: () => 'Date not provided',
      Just: date => date.toLocaleDateString()
    },
    maybeDate
  );
```

Here, we are introducing another new `Maybe`API: the `caseOf()` function.

`caseOf<A, B>(caseof: {Just: (v: A) => B; Nothing: () => B;}, value: Maybe<A>): B`

In the `userDob` function, we don’t want to use `withDefault` because we need to perform some logic with the extracted value before returning it, and that’s precisely what the `caseOf()` function is useful for.

This function gives you an opportunity to make computations before returning the value.

### `Maybe`-fying existing APIs

There’s one last thing that needs to be done to complete our application: we need to render our user cards.

The rendering logic involves dealing with DOM APIs, we need to get a reference to the `div` element where we want to insert our Html markup, and we’ll use the `getElementById(elementId: string): null | HTMLElement` function.

In our newly acquired obsession to avoid `null` and `undefined`, we’ve decided to create a `_Maybefied_` version of this function to avoid dealing with `null`.

```jsx
const maybeGetElementById = (id: string): Maybe<HTMLElement> => {
  const elem = document.getElementById(id);
  return elem === null ? nothing() : just(elem);
};
```

Now the compiler won’t let us treat the result like it’s an `HTMLElement`, when in reality it could entirely be `null` if the `div` we are looking for is not in our page. `Maybe` has us covered.

Let’s use this function and render those user cards:

```jsx
const maybeAppDiv: Maybe<HTMLElement> = maybeGetElementById('app');

caseOf(
  {
    Just: appDiv => {
      appDiv.innerHTML = '<h1>Users</h1>' + usersJson
        .map(userJson => userCard(userParser(userJson)))
        .join('<br>');
      return;
    },
    Nothing: () => {
      document.body.innerHTML = 'App div not found';
      return;
    }
  },
  maybeAppDiv
);
```

You can play with the code of this example [here](https://stackblitz.com/edit/typescript-nt8mej).

We’ve seen just a few of the available `Maybe` APIs. You can do much more with this data type.

Go check the [ts.data.maybe](https://github.com/joanllenas/ts.data.maybe) docs page to find out more.

### The `Either` data type

Errors are an essential part of software development, ignore them and your program will fail to meet the user’s expectations.

### Defining failure

As always, semantics are fundamental, and defining failure consistently is vital to making our programs easier to reason about.

So, what exactly defines failure? Let’s see some examples of the kind of errors you can find around:

- An operation that throws a runtime exception.
- An operation that returns an `Error` instance.
- An operation that returns `null`.
- An operation that returns an object with `{error: true}` in it.
- When we reach the `catch()` clause in a `Promise.`

Most of the time, you’ll handle errors by branching your logic with `if` and `try catch` statements.

The resulting code can get messy quite rapidly because of the depth of nesting and intermediate variables that need to be defined to `_transport_` the final result from one point of your code to another.

### `Either` to the rescue

Wouldn’t it be nice if we could abstract all those `if` and `try catch` statements and reduce the number of intermediate variables that need to be defined?

There’s already something that does all that: it’s the `Either` data Type (also known as `Result`).  
`Either` encapsulates the idea of a computation that may fail.

An `Either` value can either be `Right` of some value or `Left` of some error.

```jsx
type Either<T> = Right<T> | Left;
```

Looks familiar, right? It’s very similar to the `Maybe` type signature, although you’ll see how they differ in a moment.

We are going to use the [ts.data.either](https://github.com/joanllenas/ts.data.either) Library. Let’s get familiar with its API.

### The `Either` candidates

This time we are going to create a `getUserById` service that searches a user by id from a json file.  
The service does the following:  
1. Validates that the Json file name is valid.  
2. Reads the Json file.  
3. Parses the Json into an object Graph.  
4. Finds the user in the array.  
5. Returns.

As you can see, every step has the potential for failure. That’s fine because we are going to use `Either` to keep errors under control.

### Some utils for our example

Let’s create a few things our example relies on to work.

First, we are going to reuse the `UserJson` Type from the previous example:

```jsx
export interface UserJson {
  id: number;
  nickname: string;
  email: string;
  bio?: string | null;
  dob?: string | null;
}
```

We also need a (virtual) file system.

```jsx
const fileSystem: { [key: string]: string } = {
  "something.json": `
        [
          {
            "id": 1,
            "nickname": "rick",
            "email": "rick@c137.com",
            "bio": "Rick Sanchez of Earth Dimension C-137",
            "dob": "3139-03-04T23:00:00.000Z"
          },
          {
            "id": 2,
            "nickname": "morty",
            "email": "morty@c137.com",
            "bio": null,
            "dob": "2005-04-08T22:00:00.000Z"
          }
        ]`
};
```

We need a `readFile(filename: string): string;` function for our virtual file system that returns the file contents as a string if the file is found or throws an exception otherwise.

```jsx
const readFile = (filename: string): string => {
  const fileContents = fileSystem[filename];
  if (fileContents === undefined) {
    throw new Error(`${filename} does not exists.`);
  }
  return fileContents;
};
```

Finally, a (quick and dirty) `pipeline` function implementation, which will allow us to make our function calls flow similar to how fluent APIs do:

> There are some libraries out there that do the same in a Typesafe way, but I didn’t want to include yet another dependency.  
> And there’s already a [native JavaScript pipeline API implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Pipeline_operator) in the works!

```jsx
export const pipeline = (initialValue: any, ...fns: Function[]) =>
  fns.reduce((acc, fn) => fn(acc), initialValue);
```

So, instead of calling multiple functions like this:

```jsx
add1( add1( add1( 5 ) ) ); // 8
```

We can make it like this:

```jsx
pipeline(
    5,
    n => add1(n), // we could go point-free and just use `add1`
    n => add1(n),
    n => add1(n)
); // 8
```

### `Either` composition

Our `getUserById` function is, in fact, a sequence of actions where the next depends on the outcome of the previous.

Each step does something that may fail and passes the result to the next one, and because of that, the best way to represent each of these steps is with functions returning `Either`.

### 1. Validating the Json filename

```jsx
const validateJsonFilename = (filename: string): Either<string> =>
    filename.endsWith(".json")
      ? right(filename)
      : left(new Error(`${filename} is not a valid json file.`));
```

Here we introduce the `Left` and `Right` constructor functions:

```jsx
left(error: Error): Either;
```

```jsx
right(value: T): Either;
```

The logic is quite straightforward (and naive): If the file doesn’t have `.json` extension, we return a `Left`, which means there was an error, otherwise a `Right` with the filename.

### 2. Reading the Json file

```jsx
const readFileContent = (filename: string): Either<string> =>
    tryCatch(() => readFile(filename), err => err);
```

As we saw previously, the `readFile` function throws an exception if the file is not found.  
To control runtime errors `Either` has the `tryCatch` function:

```jsx
tryCatch<A>(f: () => A, onError: (e: Error) => Error): Either<A>
```

This function wraps logic that may throw and returns an `Either` instance.

`tryCatch` accepts two function parameters, one that is executed on success and another on failure.

On success, the result is returned wrapped in a `Right`, on failure the error generated from the source function is passed to the error handler and the result is returned wrapped in a `Left`.

### 3. Parsing Json

```jsx
const parseJson = (json: string): Either<UserJson[]> =>
    tryCatch(
      () => JSON.parse(json),
      err => new Error(`There was an error parsing this Json.`)
    );
```

Nothing new here, we use `tryCatch` because `JSON.parse` throws on failure.

### 4. Finding the user in the array

After all this error juggling it is time to search for the user, but let’s think about it, what happens if the provided `id` doesn’t match any user, should we return `null`, `undefined` or maybe `Left`? Oh! Remember `Maybe`? Let’s use it here too!

```jsx
const findUserById = (users: UserJson[]): Either<Maybe<UserJson>> => {
  return pipeline(
    users.find(user => user.id === id),
    (user: UserJson) =>
      user === undefined ? nothing<UserJson>() : just(user),
    (user: Maybe<UserJson>) => right(user)
  );
};
```

Wow, look at that return.

```jsx
Type signature Either<Maybe<UserJson>>
```

There’s so much stuff packed in so few characters… let’s recap:  
– `Either` -> contains a value or an error.  
– `Maybe` -> contains something or nothing.  
– `UserJson` -> contains a `UserJson`.

So, just by reading the signature `findUserById(users: UserJson[]): Either;` you know for sure that `findUserById` is part of an operation that might have failed (`Either`) and returns a `UserJson` that can be empty (`Nothing`). Not a small feat!

### 5. Returning a value

At this point, we have all the ingredients needed to declare our `getUserById` service. Let’s put it all together.

```jsx
const getUserById = (filename: string, id: number): Either<Maybe<UserJson>> => {
  const validateJsonFilename = (filename: string): Either<string> =>
    filename.endsWith(".json")
      ? right(filename)
      : left(new Error(`${filename} is not a valid json file.`));

  const readFileContent = (filename: string): Either<string> =>
    tryCatch(() => readFile(filename), err => err);

  const parseJson = (json: string): Either<UserJson[]> =>
    tryCatch(
      () => JSON.parse(json),
      err => new Error(`There was an error parsing this Json.`)
    );

  const findUserById = (users: UserJson[]): Either<Maybe<UserJson>> => {
    return pipeline(
      users.find(user => user.id === id),
      (user: UserJson) =>
        user === undefined ? nothing<UserJson>() : just(user),
      (user: Maybe<UserJson>) => right(user)
    );
  };

  return pipeline(
    filename,
    (fname: string) => validateJsonFilename(fname),
    (fname: Either<string>) => andThen(readFileContent, fname),
    (json: Either<string>) => andThen(parseJson, json),
    (users: Either<UserJson[]>) => andThen(findUserById, users)
  );
};
```

The only thing this function adds to what we’ve done in the four previous steps is compose them all in a pipeline, where each operation feeds its resulting `Either` to the next one thanks to this new `Either` Api we just introduced, the `andThen` function:

```jsx
andThen<A, B>(f: (a: A) => Either<B>, value: Either<A>): Either<B>
```

This function basically says:  
— Give me an `Either` and I’ll return you another `Either` using this function that returns `Either` that you have to provide as well.

The way this function `pipeline` flows is as follows:  
1. Provide an initial value.  
2. Execute this function, if it fails, return the error in a `Left`, otherwise return the resulting value in a `Right`.  
3. If we got a `Left` from the previous function return that `Left`, otherwise execute this function, if it fails, return the error in a `Left`, otherwise, return the resulting value in a `Right`.  
4. If we got a `Left` from the previous function return that `Left`, otherwise execute this function, if it fails, return the error in a `Left`, otherwise, return the resulting value in a `Right`.  
5. If we got a `Left` from the previous function return that `Left`, otherwise execute this function, if it fails, return the error in a `Left`, otherwise, return the resulting value in a `Right`.

Did you notice that steps 3, 4 and 5 are the same? And that would be true for all intermediate operations that this pipeline might have. Once you get the idea, everything flows.

### Using our `getUserById` service

Our service returns a `UserJson` buried two levels deep, one is an `Either` and the other is a `Maybe`. Let’s extract this valuable information from our container types.  
The `printUser` function extracts the `UserJson` from the `Maybe`.

```jsx
const printUser = (maybeUser: Maybe<UserJson>) =>
  maybeCaseOf(
    {
      Nothing: () => "User not found",
      Just: user => `${user.nickname}<${user.email}>`
    },
    maybeUser
  );
```

> Here, `maybeCaseOf` is an alias becasue both `Either` and `Maybe` have a function called `caseOf` that we use in the same source file.  
> You can create an alias importing the function like this: `import { caseOf as maybeCaseOf } from "ts.data.maybe";`

And finally! Let’s tie everything together:

```jsx
console.log(
    caseOf(
      {
        Right: user => printUser(user),
        Left: err => `Error: ${err.message}`
      },
      getUserById("something.json", 1)
    )
); // rick<rick@c137.com>

console.log(
    caseOf(
      {
        Right: user => printUser(user),
        Left: err => `Error: ${err.message}`
      },
      getUserById("something.json", 444)
    )
); // User not found

console.log(
    caseOf(
      {
        Right: user => printUser(user),
        Left: err => `Error: ${err.message}`
      },
      getUserById("nothing.json", 2)
    )
); // Error: nothing.json does not exists.

console.log(
    caseOf(
      {
        Right: user => printUser(user),
        Left: err => `Error: ${err.message}`
      },
      getUserById("noExtension", 2)
    )
); // Error: noExtension is not a valid json file.
```

You can play with the code of this example [here](https://stackblitz.com/edit/typescript-ahjaw5).

We’ve seen just a few of the available `Either` APIs, and you can do much more with this data type.  
Go check the [ts.data.either](https://github.com/joanllenas/ts.data.either) docs page to find out more.

### Conclusion

We’ve learned that container Types are wrappers for values that provide APIs so we can safely operate with them.

The `Maybe` container Type makes explicit the concept of emptiness, instead of relying on the inferior semantic and error-prone alternatives `null` and `undefined` we have this wrapper at our disposal that has a clearly defined API and semantic meaning.

The `Either` container Type encapsulates the concept of failure and offers an alternative to the verbosity of branching our code in `if` and `try catch` statements.

The clearly-defined composable APIs exposed by this type infect our programs, making them more functional, clean and more comfortable to read and reason about.

* * *
## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps
 
![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)
 
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
[Try it for free](https://logrocket.com/signup/).
* * *

The post [Safer code with container types (Either and Maybe)](https://blog.logrocket.com/safer-code-with-container-types-either-and-maybe/) appeared first on [LogRocket Blog](https://blog.logrocket.com).