Bounded types in fp-ts
Tags: functional, typescript
Published: 2021-07-03 13:57:37

[TLDR; show me the code!](https://codesandbox.io/s/fp-ts-bounded-forked-80mho?file=/src/fpts.ts)

A `Bounded<a>` is used to name the upper and lower limits within the `a` type for your domain. So for example, in this example, I am going to use a `Bounded<number>` from the [fp-ts](https://gcanti.github.io/fp-ts/) library to represent the age of a person within the bounds of 18 & 100.

```
import * as B from "fp-ts/lib/Bounded";
import * as O from "fp-ts/lib/Ord";

const AgeBound: B.Bounded<number> = {
  bottom: 18,
  top: 100,
  equals: O.ordNumber.equals,
  compare: O.ordNumber.compare
};
```

However, this does not actually do the checking of the bounds for you. So here I am creating a function that takes in a generic `A` type and a `Bounded<A>` to return back an `Option<A>`. 

```
import * as Opt from "fp-ts/lib/Option";

// Bounded<A> -> A -> Option<A>
const mkBounded: <A>(bounded: B.Bounded<A>) => (a: A) => Opt.Option<A> = (
  bounded
) => (a) => {
  if (
    bounded.compare(a, bounded.bottom) !== -1 &&
    bounded.compare(a, bounded.top) !== 1
  ) {
    return Opt.some(a);
  }
  return Opt.none;
};
```

At this point, we realise that we haven't actually created an `Age` type. We also don't want to use just a `number` as this will allow values outside our domain to typecheck so we will also use an intersection type to create a new-type. We will also export the type so that other files can import that type.
```
// Age as a newtype
export type Age = number & { readonly __tag: unique symbol };
```

We now have all the pieces to create a smart constructor to return back an Option<Age>. Note that this is exported to allow other files to be able to create the `Age` type outside of typecasting.

```
import { flow } from "fp-ts/lib/function";

// smart constructor to return a Option<Age>
// number -> Option<Age>
export const mkAge = flow(
  mkBounded(AgeBound),
  Opt.map((age) => age as Age)
);
```
There is a bit to unpack here. First `mkBounded(AgeBound)` will return back a `number -> Option<number>` which is the wrong type as we need a `number -> Option<Age>`, hence we use a `Opt.map(age => age as Age)` which has the type `Option<number> -> Option<Age>`. We then use `flow` to perform left to right function composition and we end up with the right `number -> Option<Age>` shape for our function.

```
console.log(mkAge(9)); // None
console.log(mkAge(18)); // Some<18>
console.log(mkAge(20)); // Some<20>
console.log(mkAge(100)); // Some<100>
console.log(mkAge(101)); // None
```

So what's the point of doing all this? The reason is that we now have a narrower type to represent our `Age` and can rely more on our typechecker to "make illegal states unrepresentable".

```
const x: Age = 2.1; //typescript error
```

Another situation where the idea of narrowing a type arises when we receive JSON at runtime and wish to convert the data inside the JSON into our own stricter types within our domain. Check in next time for an article around the same concepts using [io-ts](https://gcanti.github.io/io-ts/).


### References
* [Functional Programming in Typescript](https://leanpub.com/functionalprogrammingintypescriptapracticalguide) - Sam Van Overmeire
* [Newtype in typescript](https://kubyshkin.name/posts/newtype-in-typescript/) - Dmitriy Kubyshkin
* [Using fp-ts and io-ts: types and implementation](https://dev.to/ruizb/using-fp-ts-and-io-ts-types-and-implementation-1k6a) - Benoit Ruiz
* [Parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) - Alexis King