Enough fp-ts to work with io-ts
Tags: functional, fpts, typescript
Published: 2019-12-19 14:44:39

---
title: Enough fp-ts to work with io-ts
published: true
description: A look into what is needed from fp-ts to work with io-ts
tags: functional,fp-ts,typescript
cover_image: https://thepracticaldev.s3.amazonaws.com/i/yufd1tdiyyngyr6hhzz0.jpg
series: fp-ts et al
---

This is a small tutorial of what one needs to know about [fp-ts](https://github.com/gcanti/fp-ts) to work with [io-ts](https://github.com/gcanti/io-ts). In other words:

> Just enough fp-ts to ~~to work with io-ts~~ be dangerous

## what-ts now ?

Let's start by introducing these two libraries and what they are.

**fp-ts**

> Typed functional programming in TypeScript
>
> ... a library containing implementations of common algebraic types in TypeScript

That seems to be clear. Ok, maybe not the [algebraic types](https://en.wikipedia.org/wiki/Algebraic_data_type) but that's not super important in practice.

**io-ts**

> Runtime type system for IO decoding/encoding

Urhg, what?!

io-ts provides safe encoding/decoding. We can say is the proper way to do `JSON.parse` with types in mind. Check the library docs to learn more about it.

## But why?

io-ts is part of the [ecosystem of typed functional programming (FP) in TypeScript](https://gcanti.github.io/fp-ts/introduction/ecosystem.html), fp-ts being the main library. So naturally io-ts does good use of fp-ts.

If you are familiar with (typed) FP, then why are you still reading? Or maybe you can finish and suggest some improvements to this post :sweat_smile:

On the other hand, if you aren't familiar with (typed) FP and see the [fp-ts website](https://gcanti.github.io/fp-ts/) it might seem scary and feel like a lot of information to process. Specially the weird words. Well it is a bit scary, I agree with you. But for working with io-ts we need just a really ~~small~~ smol fraction of the library. Besides that, my advice is to approach it as you would approach any other API of a library you learning to use. I'm not gonna go full _advocate of FP_ mode, just say that it's really worth to learn and a pleasure to work with.

## Either it's right or left

When decoding a type with io-ts the ~~weird~~ new thing we see is, it returns a value of type `Either<t.Errors, T>` (where `T` is the type we want to decode to).

```ts
import * as t from 'io-ts'

const result: Either<t.Errors, number> = t.number.decode(123)
```

Either is an algebraic type, in particular a [discriminated union](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions) with two branches, `Right` and `Left`. The first is usually used to denote success and the later to denote failure. In our example `t.Errors` is the type wrapped by Left and `number` is wrapped by Right. Either is a [sum type](https://en.wikipedia.org/wiki/Tagged_union) (another term for discriminated unions) because it can be either (pun intended) Left or Right, but not both.

The purpose of Either, as you probably guessed, is to work with operations that can fail or succeed. And it provides some nice features to work with it.

### `fold`ing :open_file_folder:

Although one could access the internals of an Either value to get it's contents, that's not how it's intended to work.

To get a value out of it, fp-ts provides `fold`:

```ts
declare function fold<E, A, B>(
  onLeft: (e: E) => B,
  onRight: (a: A) => B,
): (e: Either<E, A>) => B
```

As the type definition implies, it expects us to handle both cases.

There are some similarities between `fold` and `Array.reduce`, which is also a way to transform a wrapped type (`Array<A>`) into a single value (`B`).

```ts
declare function reduce<A, B>(
  f: (acc: B, current: A) => B,
  initialValue: B,
): (arr: Array<A>) => B
```

NOTE: _I changed the signature `of Array.reduce` to match the signature of Either's fold_.

We are going to use `fold` mostly as the way to consume an Either. Eg. after decoding an API response with io-ts you'll either dispatch a success action (`onRight`) or a failure one and report to Honeybadger the error (`onLeft`).

You'll see that most (if not all) of the algebraic types that fp-ts defines have their own version of `fold` :mag:

### `map`ing :world_map:

Once we have `fold` in our toolbelt we might be tempted to extract values out of Either every time we want to work with them.

What if I told you, you don't have to?

Just like `Array.map`, there's also `map` defined for Either.

```ts
// Array's map
declare function map<A, B>(f: (a: A) => B): (arr: Array<A>) => Array<B>

// Either's map
declare function map<A, B>(f: (a: A) => B): <E>(e: Either<E, A>) => Either<E, B>
```

NOTE: _I changed the signature of `Array.map` to match Either's one_.

As we can see, it allows us to transform the Right value of an Either by applying a function to its contents. If our value happens to be a Left, it won't be changed. And we can keep working happily with our wrapped value, no `fold`ing required.

What about transforming the Left value you ask? `mapLeft` to the rescue, and this time the unchanged one is the Right.

```ts
declare function mapLeft<E, G>(
  f: (e: E) => G,
): <A>(fa: Either<E, A>) => Either<G, A>
```

### Constructing Eithers :construction_worker_woman:

The last thing we need to know about Either is how to construct values.

```ts
import {left, right, Either} from 'fp-ts/lib/Either'

declare function left<E = never, A = never>(e: E): Either<E, A>

declare function right<E = never, A = never>(a: A): Either<E, A>

const aRight: Either<never, number> = right(123)

const aLeft: Either<string, never> = left('some error message')
```

## [Go Through The Pipes](https://www.youtube.com/watch?v=HioMxPJj59U)

Although strictly that's all we need from fp-ts to work with io-ts, we are also going to be using a few utilities it provides to work with functions. It's a FP library after all, right?

### pipe :shower:

This is one of the ways to do function composition in fp-ts. It let's us _pipe_ a value through a list of functions, where the next function is called with the result of the previous one.

Take this case for example:

```ts
export const decodeWith = <A>(decoder: Decoder<unknown, A>) => (
  response: unknown,
) =>
  mapLeft((errors) => ({tag: 'decoding', errors} as const))(
    humanizeErrors(decoder.decode(response)),
  )
```

We decode a response, humanize the potential errors and then map the left to another type.

With pipe it would look like this:

```ts
import { pipe } from 'fp-ts/lib/pipeable';

export const decodeWith = <A>(decoder: Decoder<unknown, A>) => (
  response: unknown,
) =>
  pipe(
    response,
    decoder.decode,
    humanizeErrors,
    mapLeft((errors) => ({tag: 'decoding', errors} as const)),
  )
```

Now the code reads just like the description:

> decode a response, humanize the potential errors and then map the left to another type.

One good thing about pipe is, it's ready for when [the pipeline operator proposal](https://github.com/tc39/proposal-pipeline-operator) lands in JS. Sadly, judging by the pace TC39 works at, that might take years.

```diff
-import { pipe } from 'fp-ts/lib/pipeable';
-
 export const decodeWith = <A>(decoder: Decoder<unknown, A>) => (
   response: unknown,
 ) =>
-  pipe(
-    response,
-    decoder.decode,
-    humanizeErrors,
-    mapLeft((errors) => ({tag: 'decoding', errors} as const)),
-  )
+  response
+    |> decoder.decode
+    |> humanizeErrors
+    |> mapLeft(errors => ({ tag: 'decoding', errors } as const))
```

### flow :email:

`flow` is right to left function composition. Very similar to pipe, but doesn't take the value as first argument but instead returns a function.

```diff
-import { pipe } from 'fp-ts/lib/pipeable';
+import { flow } from 'fp-ts/lib/function';
 
-export const decodeWith = <A>(decoder: Decoder<unknown, A>) => (
-  response: unknown,
-) =>
-  pipe(
+export const decodeWith = <A>(decoder: Decoder<unknown, A>) =>
+  flow(
-    response,
     decoder.decode,
     humanizeErrors,
     mapLeft((errors) => ({tag: 'decoding', errors} as const)),
   )
```

## That's it but there's more

As far as using io-ts, that's all we need to know about fp-ts (for now at least). If you are already bored, no need to keep on reading.

Although we could use fp-ts (and it's ecosystem) for many other things, this is a great start. By decoding all IO stuff (a.k.a. API responses) we make sure to remove unexpected runtime behavior and errors because we promised the compiler a `JSON.parse` returns something when in reality it doesn't.

If you see the value on it and want to learn more about (typed) FP check out the [Core Concepts](https://gcanti.github.io/fp-ts/introduction/core-concepts.html) and [Learning Resources](https://gcanti.github.io/fp-ts/introduction/learning-resources.html) sections in the fp-ts docs. And more importantly make sure to ask questions and look for other people that are in the path of learning about (typed) FP as well.

## No M word? :burrito:

Somehow I managed to write about FP without using the word monad (well, now I did). That is because there was no need to do so. Even though Either is a monad, we didn't use any of it's monadic properties. We did use it as a Functor. A functor is an algebraic type that has `map` defined to it, which [has to abide some laws](https://wiki.haskell.org/Functor#Functor_Laws) to make sure every functor instance is consistent. You wouldn't want Either's map behaving differently than Array's map. Oh and yeah, Array is also a Functor.

Once again, learning these terms (monad, functor, applicative, foldable, _et al_) is not necessary. But, if you are interested, it can be really useful. For example, if you know that Array has map defined because it's a functor. And then discover a new algebraic type (like Either) that is also a functor you'll already know how to work with the map for that type.

---

Ok, now that's it.

**Happy and safe coding** :nerd_face: