Intro to fp-ts
Tags: functional, typescript
Published: 2021-08-29 15:43:37

This article serves as an introduction to fp-ts coming from someone knowledgeable in JS. I was inspired to write this up to help some team members familiarise themselves with an existing codebase using fp-ts.

## Hindley-Milner type signatures
Hindely-Milner type signatures represent the shape of a function. This is important because it will be one of the **first** things you look at to understand what a function does. So for example:
```
const add1 = (num:number) => num + 1
```
This will have the type signature of `number -> number` as it will take in a number and return a number.

Now what about functions that take multiple arguments, for example a function `add` that adds two numbers together? Generally, **functional programmers prefer to functions to have one argument**. So the type signature of `add` will look like `number -> number -> number` and the implementation will look like this: 
```
const add = (num1: number) => (num2: number) => num1 + num2
```
Breaking this down, we don't have a function that takes in two numbers and adds them, we have a function that takes in a number and returns another function that takes in an number that finally adds them both together.

In the fp-ts documentation, we read the typescript signature to tell us what a function does. So for example the [trimLeft](https://gcanti.github.io/fp-ts/modules/string.ts.html#trimleft) function in the string package has a signature of 
```
export declare const trimLeft: (s: string) => string
```
which tell us that it is a function that takes in a string and returns a string.

## Higher kinded types
Similar to how higher order functions like `map` require a function to be passed in, a higher kinded type is a type that requires another type to be passed in. For example,
```
let list: Array<string>;
```
Array is a higher kinded type that requires another type `string` to be passed in. If we left it out, typescript will complain at you and ask you "an array of what?". An example of this in fp-ts is the [flatten](https://gcanti.github.io/fp-ts/modules/Array.ts.html#flatten) function from array. 
```
export declare const flatten: <A>(mma: A[][]) => A[]
```
What this says is that the function requires another type `A` and it flattens arrays of arrays of A into arrays of A.

However, arrays are not the only higher kinded types around.  I like to think of higher kinded types as containers that help abstract away some concept. For example, arrays abstract away the concept of iteration and Options abstract away the concept of null.

## Option
Options are a higher kinded type that abstract away the concept of null. Although it requires some understanding to use and some plumbing to get it all set up, **my promise to you is that if you start using Options, your code will be more reliable and readable.**

Options containers for optional values.
```
type Option<A> = None | Some<A>
```
At any one time, an option is either a `None` representing null or `Some<A>` representing some value of type `A`. 

If you have a function that returns an Option for example [head](https://gcanti.github.io/fp-ts/modules/Array.ts.html#head)
```
export declare const head: <A>(as: A[]) => Option<A>
```
By seeing that the function returns an Option, you know that by calling head, you may not get a value. However, by wrapping this concept up in an Option, *you only need to deal with the null case when you unwrap it*.

So how do you write your own function that returns an Option? If you are instantiating your own Options, you will need to look under the [constructors](https://gcanti.github.io/fp-ts/modules/Option.ts.html#constructors) part of the documentation. For example,
```
import { some, none } from "fp-ts/lib/Option";

const some1 = (s:string):Option<number> => s === 'one'? some(1) : none;
```
However to extract out the value inside an Option, you will need to use one of the [destructor](https://gcanti.github.io/fp-ts/modules/Option.ts.html#destructors) methods. For example, the `fold` function in Option is a destructor.
```
export declare const fold: <A, B>(onNone: Lazy<B>, onSome: (a: A) => B) => (ma: Option<A>) => B
```
This type signature is a little complicated so let's break it down. 
- `fold: <A, B>...`:This function has two type parameters A & B
- `...(onNone:Lazy<B>,...`: This take in an onNone function that returns a value of type `B`
- `..., onSome: (a: A) => B)...`: This also takes in an onSome function that takes in a value of type `A` and returns a value of type `B`
- `... => (ma: Option<A>)...`: This expects an Option of type `A` to be passed in
- `... => B`: After all arguments are passed in, this will return a value of type B.

Putting all this together, if we wanted to use our `some1` function from earlier and print "success 1" if the value was "one" otherwise print "failed", it would look like this:

```
import { some, none, fold } from "fp-ts/lib/Option";

const some1 = (s:string):Option<number> => s === 'one'? some(1) : none;

const print = (opt:Option<number>):string => {
  const onNone = () => "failed";
  const onSome = (a:number) => `success ${a}`;
  return fold(onNone, onSome)(opt);
}

console.log(print(some1("one")));
console.log(print(some1("not one")));
```

Now we know how to create an Option as well as extract out a value from an Option, however we are missing what in my opinion is the exciting part of Options which is the ability to transform them. Options are *Functors* which is a fancy way of saying that you can map them. In the documentation, you can see that Option has a [Functor](https://gcanti.github.io/fp-ts/modules/Option.ts.html#functor) instance and a corresponding [map](https://gcanti.github.io/fp-ts/modules/Option.ts.html#map) instance operation.
What this means is that you can transform Options using regular functions. For example, if you wanted to write a function that adds one to a an `Option<number>` it would look like so:
```
import { map, Option } from "fp-ts/lib/Option";
const add1 = (num: number) => num + 1;

const add1Option = (optNum:Option<number>):Option<number> => map(add1)(optNum);
```

Now we know how to create options, transform them via map functions and use destructors to extract out the value from them whilst referring to the documentation each step of the way.