# Practical Guide to Fp-ts P2: Option, Map, Flatten, Chain

<div style="border: 2px solid #FF4136; border-radius: 10px; padding: 20px; background-color: #FEEBEC;">
  <h3 style="color: #FF4136; margin-top: 0;">⚠️ Disclaimer Warning</h3>
  <p style="font-size: 18px; line-height: 1.6;">
    <strong>fp-ts</strong> is now dead. Please use <a href="https://effect.website/" target="_blank" style="color: #FF4136; text-decoration: none;"><strong>effect-ts</strong></a> instead.
  </p>
  <p style="font-size: 16px; line-height: 1.4; color: #333; margin-top: 10px;">
    <em>Update 2024: effect-ts is the recommended library for functional programming in TypeScript.</em>
  </p>
</div>

## Introduction

This is the second post in my series on learning fp-ts the practical way. In my first post, I introduced the building blocks of fp-ts: [pipe](https://gcanti.github.io/fp-ts/modules/function.ts.html#pipe) and [flow](https://gcanti.github.io/fp-ts/modules/function.ts.html#flow). In this post, I will introduce the [Option](https://gcanti.github.io/fp-ts/modules/Option.ts.html) type.

## Options

Options are containers that wrap values that could be either `undefined` or `null`. If the value exists, we say the Option is of the `Some` type. If the value is `undefined` or `null`, we say it has the `None` type.

In fp-ts, the Option type is a discriminated union of `None` and `Some`.

```ts
type Option<A> = None | Some<A>
```

Why should we use Option types in the first place? Typescript already has good ways to deal with `undefined` or `null` values. For example, we can use [optional chaining](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining) or [nullish coalescing](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing).

Option types are useful because it gives us superpowers. The first superpower is the `map` operator.

## Map

The map operator allows you to transform or intuitively _map_ one value to another. Here is an example of a map function using the `pipe` operator and an anonymous function.

```ts
const foo = {
  bar: 'hello',
}

pipe(foo, (f) => f.bar) // hello
```

In this example, `foo` is mapped to `foo.bar` and we get the result `'hello'`. Let's extend this further to handle the case where `foo` is possibly `undefined` using optional chaining.

```ts
interface Foo {
  bar: string
}

const foo = {
  bar: 'hello',
} as Foo | undefined

pipe(foo, (f) => f?.bar) // hello
```

As expected, we get `'hello'` again. But we can do better here. We have a named variable `f` in our anonymous function. In general, we want to avoid this. This is because it puts us at risk of shadowing an outer variable. Another reason is the difficulty of naming the variable. It is named `f`, but you could name it `nullableFoo`. Bottom line is, there is no good name for this variable.

Let's use object destructuring to solve this problem.

```ts
pipe(foo, ({ bar }) => bar) // Property 'bar' does not exist on type 'Foo | undefined'.ts (2339)
```

> Property 'bar' does not exist on type 'Foo | undefined'.ts (2339)

Oops. The compiler can't destructure an object that is possibly `undefined`.

Enter the Option type.

```ts
pipe(
  foo,
  O.fromNullable,
  O.map(({ bar }) => bar),
) // { _tag: 'Some', value: 'hello' }
pipe(
  undefined,
  O.fromNullable,
  O.map(({ bar }) => bar),
) // { _tag: 'None' }
```

After replacing the anonymous function with a `map` function from the Option module, the compiler no longer complains. Why is this?

Let's start by looking at the output of both pipes. In the first pipe, we have `{ _tag: 'Some', value: 'hello' }`. This is in comparison to the original output, `'hello'`. Likewise, the second pipe does not output `undefined` but instead, outputs `{ _tag: 'None' }`.

Intuitively, this must imply our map function is not operating over the raw values: `'hello'` or `undefined` but rather, over a container object.

The second operation in our `pipe` function, `O.fromNullable` creates this container object. It _lifts_ the nullable value into the container by adding a `_tag` property to discriminate whether the Option is `Some` or `None`. The value is dropped if the `_tag` is `None`.

Going back to our `map` function. How does `O.map` work over the Option container? It works by performing a comparison over the `_tag` property. If the `_tag` is `Some`, it transforms the value using the function passed into `map`. In this case, we transformed it using `({ bar }) => bar`. However, if the `_tag` is `None`, no operation is performed. The container remains in the `None` state.

## Flatten

How would we handle a situation where the object has sequentially nested nullable properties? Let's extend the example we had above.

```ts
interface Fizz {
  buzz: string
}

interface Foo {
  bar?: Fizz
}

const foo = { bar: undefined } as Foo | undefined

pipe(foo, (f) => f?.bar?.buzz) // undefined
```

To make this work with optional chaining, we only needed to add another question mark. How would this look like using the Option type?

```ts
pipe(
  foo,
  O.fromNullable,
  O.map(({ bar: { buzz } }) => buzz),
)
```

> Property 'buzz' does not exist on type 'Fizz | undefined'.ts (2339)

Sadly, we run in the same problem we had before. That is, object destructuring cannot be used over a type that is possibly `undefined`.

What we can do is _lift_ both `foo` and `bar` into Option types using `O.fromNullable` twice.

```ts
pipe(
  foo,
  O.fromNullable,
  O.map(({ bar }) =>
    pipe(
      bar,
      O.fromNullable,
      O.map(({ buzz }) => buzz),
    ),
  ),
) // { _tag: 'Some', value: { _tag: 'None' } }
```

But now we've created two new problems. First, it's horribly verbose. Second, we have a nested Option. Look at the `_tag` of both the outer and inner Option. The first one is `Some`, which we expect because `foo.bar` is defined. The second one is `None` because `foo.bar.buzz` is `undefined`. If you only cared about the result of the final Option, you would need to traverse the Option's nested list of tags every time.

Given that we only care about the final Option, could we _flatten_ this nested Option into a single Option?

Introducing the `O.flatten` operator.

```ts
pipe(
  foo,
  O.fromNullable,
  O.map(({ bar }) =>
    pipe(
      bar,
      O.fromNullable,
      O.map(({ buzz }) => buzz),
    ),
  ),
  O.flatten,
) // { _tag: 'None' }
```

We now have a single Option, `{ _tag: 'None' }` that represents the last Option in the pipeline. If we wanted to check whether this Option was `Some` or `None`, we can pipe the result into [O.some](https://gcanti.github.io/fp-ts/modules/Option.ts.html#issome) or [O.none](https://gcanti.github.io/fp-ts/modules/Option.ts.html#isnone).

However, we still have the problem of verbosity. It would be beneficial if we could both map and flatten the nested option with a single operator. Intuitively, this is often called a _flatmap_ operator.

## Chain (Flatmap)

In fp-ts, the flatmap operator is called `chain`. We can refactor the above code into the following.

```ts
pipe(
  foo,
  O.fromNullable,
  O.map(({ bar }) => bar),
  O.chain(
    flow(
      O.fromNullable,
      O.map(({ buzz }) => buzz),
    ),
  ),
) // { _tag: 'None' }
```

In short, we achieve the same result with less.

## Conclusion

In most cases, you won't need to use Option; optional chaining is less verbose. But the Option type is more than just checking for `null`. Options can be used to represent failing operations. And just like how you can lift `undefined` into an Option, you can also lift an Option into another fp-ts container, like [Either](https://gcanti.github.io/fp-ts/modules/Either.ts.html#fromoption).

Checkout the official [Option documentation](https://gcanti.github.io/fp-ts/modules/Option.ts.html) for more info.

