Functional programming with fp-ts.
Tags: typescript, fpts, functor, monad
Published: 2023-02-06 22:22:28

In the [previous article](https://dev.to/petrtcoi/niemnogho-ramda-dlia-react-i-redux-3id6) we looked at an example of using the `ramda` library together with `React/Redux`. Here, I want to share my experience using another great library, [fp-ts](https://github.com/gcanti/fp-ts).

These are my first steps in this direction, and I would appreciate any comments and feedback.

## Functional programming - programming with containers
If `ramda` is more about function composition, `fp-ts` is already about working with functors, monads, and so on. These are quite abstract concepts that, for simplicity, I defined as just containers.

The idea is that in the code, we work not with the values of variables as such, but rather place them in containers that store both the value of the variable and additional information about it (context). This approach makes the code more reliable by isolating potentially dangerous value options (such as null or undefined).

Let's look at the basic `Option` container, which is responsible for a variable that may or may not exist. It stores context in the _tag field, which takes two values: "None" or "Some". The first value is taken by _tag if the variable of interest is undefined or null. The second value is taken if the variable has some other value (i.e., exists).

example with `undefined`:

```typescript
import * as Option from 'fp-ts/Option';
const x = undefined      //  variable x is "undfined"
const container = Option.of(x) // put it into Option
// now its value is
// {_tag: "None"}

```
example with existing value:

``` typescript
import * as Option from 'fp-ts/Option';
const x = 55                  //  variable x exist
const container = Option.of(x)      // put it into Option
// now the value is
// {_tag: "Some", value: 55}
```

Right now, we don't have direct access to the value of the variable x. It is hidden in the Option container, and in order to access it, we need to use the map function. For example, if we want to get the result of multiplying the received variable by 2:

``` typescript
import { pipe } from 'fp-ts/function';
...
const result = pipe (
  container,
  Option.map((y) => y * 2)
)
```
On the output, we will not get the result of multiplication directly, but the result of multiplication hidden in the Option container. The type of the variable result will be as follows:

``` typescript
const result: Option.Option<number>
```
The map function performs the following steps:

1. Accepts a function foo that needs to be applied to the value stored in the container.
2. Checks the _tag value of the container.
3. If it has a value of "None", it simply returns the container as it is and does nothing.
4. If it has a value of "Some", it executes the function foo, passing the value from the value variable of the container as an argument.
5. Is the resulting value equal to null/undefined? Returns Option.None, which is {_tag: "None"}
6. If the resulting value is not equal to null/undefined, it returns it as Option.Some, which is {_tag: "Some", value: foo(x)}.

### Functors and Monads
A container that has a map function is called a functor.

In some cases, the function passed to map can itself return a container, instead of the value directly:
``` typescript
const result = pipe (
  container,
  Option.map((y) => { return Option.of(y * 5)})
)
```
If the function passed to map returns a container itself instead of a value, the resulting value will be nested inside another container. Consequently, working with such a structure is not possible.
``` typescript
const result: Option.Option<Option.Option<number>>
```
To avoid this, you need to extract `Option.Option<number>` from the extra layer of `Option`. The function that first executes the passed function and then "removes" the extra layer of the container from the resulting value is called `flatMap` or `chain` (in the case of `fp-ts`).

Essentially, this is the same function as `map`, after which the value is extracted from the outer container. If a container has such a function in its arsenal, it is already called a monad.

## Why does this make sense?

Since we do not interact with the variable's value directly, we do not have to worry about a "missing" variable appearing during code execution, leading to unpredictable results. The Option container and its map function will take care of this for us.

Placing the variable and all calculation results into containers allows us to avoid using constant checks in the code such as
```typescript
if (x == null) {
  throw ...
}
```
We know that if there is undefined or null anywhere, they will be sealed in an Option container and filtered out by the map method when applied to any function. This continues until the end of the code, where we can check what is in the container: a result (Option.Some) or an error (Option.None).

The probability of forgetting to perform the necessary check in the code is eliminated. The key is not to extract our values from the Option container. We can write code as if no errors are happening. If undefined appears somewhere, such a variable will be ignored by all functions passed to the container via map or chain.

Modern linters successfully help to avoid errors related to missing checks of variable values or computation results. But the strength of fp-ts is that its checks will work during the runtime.

## Example with REST API
Here is an example of a backend function for a hypothetical store, where a registered user tries to purchase an item by paying for it using funds stored in their balance.

The logic is as follows:

1. Check if itemId is received.
2. Check if an item with the specified itemId exists.
3. Call a method that checks the request headers and determines whether the user is authorized (for simplicity, it does not take req and simply returns some user data).
4. Check if such a user exists in our database.
5. Check if the client has enough funds in their account to purchase the item.
6. Make the appropriate entries in the database.

Here is the sample code:

``` typescript
const handler = async (req: CustomNextApiRequest, res: NextApiResponse<ResponseType>) => {
  const body = makeSerializable(req.body);
  const method = makeSerializable(req.method);

  if (method !== 'POST') {
    res.status(405).send('Wrong method');
    return;
  }

  if (!body.itemId) {
    res.status(400).send('Missed data');
    return;
  }

  const sessionUserId = await getSessionUserId();
  if (!sessionUserId) {
    res.status(403).send('No signed in user');
    return;
  }

  const user = await getUser(sessionUserId);
  if (!user) {
    res.status(400).send('Cant find user');
    return;
  }

  const item = await getItem(body.itemId);
  if (!item) {
    res.status(400).send('Cant find item');
    return;
  }

  if (!isBalanceSufficient(item, user)) {
    res.status(400).send('Balance is not sufficient');
    return;
  }

  /** some db actions */

  res.status(200).send('OK');

};
```
At each stage, a check is made to ensure that the received value is valid. If you skip the error and allow the appearance of undefined or null values in the program, the result can be unpredictable.

## Solution using fp-ts and TaskEither.
To solve this problem with fp-ts, we will need the TaskEither module. It consists of two parts:

### Either
`TaskEither` is similar to `Option`, but instead of returning `Option.None`/`Option.Some`, it returns `Either.Left` and `Either.Right` values. `Some` and `Right` are fundamentally the same - they are just containers for storing data. The `Either.Left` variant, unlike `None`, can also store a string with an error message.

The most common way to pass a value to `Either` is through the method with a self-explanatory name `fromNullable`. It takes a string with an error message as the first argument and the second argument is the value of interest, which can be null (nullable).

For example:

``` javascript
E.fromNullable('No data')(565);  // { _tag: 'Right', right: 565 }
E.fromNullable('No data')(null)  // { _tag: 'Left', left: 'No data' }
```
`Either` is preferred over `Option` since it allows us to provide information about the nature of the error.

### Task
`Task` is a wrapper for asynchronous tasks.
``` javascript
T.of(getItem)  // T.Task<(id: number) => Promise<Item | null>>
```

It is assumed that `Task` always completes successfully. In order to handle "bad" results, `TaskEither` is used. That is, a `Task` that can return both a positive result (`Either.Right`) and a negative one (`Either.Left`).


## Шаг 1. Processing User
This piece of code is a bit more complex than working with Item, so I will explain it right away, and then the code with item will become clear.

The task is to get the user wrapped in an `Either` container. That is, either the user exists (`Either.Right`), or the user does not exist (`Either.Left` with an error message in the string format).

``` javascript
const user: E.Either<string, User>  // we need to get 
```
 
the code:

``` typescript
import * as TE from 'fp-ts/TaskEither';

const user = await pipe(
    getSessionUserId,  // () => Promise<number | null>
    TE.fromTask,       // TE.TaskEither<never, number | null>
    TE.chain(
      flow(                                // get number | null
        TE.fromNullable('Not logged In'),  // TE.TaskEither<string, number>
        TE.chain(
          flow(                             // get usereId
            getUser,                        // return Promise<User | null>
            (user) => () => user,
            TE.fromTask,                                  // TE.TaskEither<never, User | null>
            TE.chain(TE.fromNullable('Cant find user')),  // TE.TaskEither<string, User>
          )),
      ),
    ),   // TE.TaskEither<string, User>
  )();

```
For convenience, I added the return values as comments after each step.

Let's go through what's happening here step by step:

``` javascript
const user = await pipe(...)()
```
The `pipe` function takes a list of functions that are called sequentially. The result of the first function is passed as an argument to the second function. The result of the second function is passed as an argument to the third function, and so on.

Since we need to get `TaskEither` as a result, I immediately called the obtained result (`await()`) to get just `Either`.
``` javascript
pipe (
    getSessionUserId, 
    TE.fromTask, 
...)
```
We take the `getSessionUserId` function and wrap it in a container (`TaskEither`) using the `TE.fromTask` method. Now we can work with it safely.
``` javascript
pipe (
    getSessionUserId, 
    TE.fromTask, 
    TE.chain (
       flow (
         ...
       )
      ...
    )
...)
```
Since the result of the `getSessionUserId` function is inside a container, we need to unpack it in order to work with it. The methods `map` and `chain` are responsible for this. Later in the code, the result will be wrapped in another `TaskEither`, so we use `chain` to avoid double nesting.

Another thing to note is that we use `flow` instead of `pipe` here. This option is more concise when you need to pass `pipe` as an anonymous function. The two options below are equivalent.
``` javascript
flow(foo,...)
(x) => pipe(x, foo...)
```
The variable passed to `flow` has the type `undefined` | `number`, so we first wrap it in a container:

``` javascript
flow (
   TE.fromNullable('Not logged In')  // TE.TaskEither<string, number>
   ...
)
```
In case the value of `getSessionUserId` was undefined, the following `TE.chain(...)` code is skipped and only the error message ('Not logged In') is returned.

In case the user was logged in, we use their id value for further work inside the next `chain` -> `flow` block.
``` javascript
flow(
    getUser,   // return Promise<User | null>
        (user) => () => user,
        TE.fromTask,  // TE.TaskEither<never, User | null>
        TE.chain(TE.fromNullable('Cant find user')),
)),

```
Here we also call the asynchronous function `getItem`, and wrap it in the `TaskEither` container. Since it requires the signature () => `Promise<User | null>`, the line (user) => () => user appears.

Then, again, we use chain and check the received result, indicating an error message in case the user is not found.

``` javascript
TE.chain(TE.fromNullable('Cant find user'))
```

## Processing Item
The process of getting the requested `Item` data is very similar. The only difference is that instead of `getSessionUserId`, we simply check whether the `itemId` was passed in the request.

``` javascript
  const item = await pipe(
    body.itemId,
    TE.fromNullable('Item ID is missed'),  // TE.TaskEither<string, number>
    TE.chain(                              // pass itemId as 35 to flow
      flow(
        getItem,                            // Promise<Item | null>
        (item) => () => item,               // () => Promise<Item | null>
        TE.fromTask,                        // TE.TaskEither<never, Item | null>
        TE.chain(TE.fromNullable('Cant find item')),
      )),
  )();

```

## Balance checking
The final step is to check if the user has enough balance to purchase the item. Here, we can't simply use `pipe` or `flow` since the check involves two variables that are wrapped in `Either` containers.

For such cases, we use the `Do` notation: we declare which variables we're going to use and give them names, then call the function with these variables using `E.chain(...)`. It looks like this:
``` javascript
pipe(
  E.Do,
  E.bind("_item", () => item),
  E.bind("_user", () => user),
  E.chain(({ _user, _item }) => isBalanceSufficient(_item, _user)
    ? E.left('Balance is not sufficient')
    : E.right('OK')
  ),
  E.fold(
    (result) => res.status(400).send(result),
    (result) => res.status(200).send(result)
  )
);
```
The function works quite simply: if the balance is enough, then it returns a "good" `E.right` value. If it is not enough, then a "bad" `E.left` value is returned with an error message.

The last function `E.fold()` takes the `E.Either<string, string>` container and checks its value (result). If the value is wrapped in `E.left`, then the first function is executed: a response with a string containing an error description and a status of `400`. If the result is positive `E.right`, then it returns "OK" with a status of `200`.

# Full code
The complete function looks like this:

``` javascript
const handler = async (req: CustomNextApiRequest, res: NextApiResponse<ResponseType>) => {
  const body = makeSerializable(req.body);
  const method = makeSerializable(req.method);

  if (method !== 'POST') {
    res.status(405).send('Wrong method');
    return;
  }


  const item = await pipe(
    body.itemId,
    TE.fromNullable('Item ID is missed'),  // TE.TaskEither<string, number>
    TE.chain(                              // pass itemId as 35 to flow
      flow(
        getItem,                            // Promise<Item | null>
        (item) => () => item,               // () => Promise<Item | null>
        TE.fromTask,                        // TE.TaskEither<never, Item | null>
        TE.chain(TE.fromNullable('Cant find item')),
      )),
  )();


  const user = await pipe(
    getSessionUserId,  // () => Promise<number | null>
    TE.fromTask,       // TE.TaskEither<never, number | null>
    TE.chain(
      flow(                                // get number | null
        TE.fromNullable('Not logged In'),  // TE.TaskEither<string, number>
        TE.chain(
          flow(                             // get usereId
            getUser,                        // return Promise<User | null>
            (user) => () => user,
            TE.fromTask,                                  // TE.TaskEither<never, User | null>
            TE.chain(TE.fromNullable('Cant find user')),  // TE.TaskEither<string, User>
          )),
      ),
    ),   // TE.TaskEither<string, User>
  )();



  pipe(
    E.Do,
    E.bind("_item", () => item),
    E.bind("_user", () => user),
    E.chain(({ _user, _item }) => isBalanceSufficient(_item, _user)
      ? E.left('Balance is not sufficient')
      : E.right('OK')
    ),
    x => x,
    E.fold(
      (result) => res.status(400).send(result),
      (result) => res.status(200).send(result)
    )
  );
  return;
};

export default handler;

```
The code can be split into separate functions, but for the sake of demonstration, this version seemed more illustrative.

Since all the values are wrapped in either `TaskEither` or `Either` containers, we don't have to worry about unforeseen scenarios.

For example, what would happen if a request came without an `itemId`:

1. At the `TE.fromNullable`('Item ID is missed') stage, since the body.itemId value is undefined, we would get `{ _tag: 'Left', left: 'Item ID is missed' }`.

2. Since the value is `Left`, the function enclosed in `TE.chain` is skipped.

3. At this stage - `E.bind("_item", () => item)` - we pass the existing value to the variable `_item`.

4. When we "unpack" the inner contents of _item in the `E.chain(...)` line, the container again sees that it is `E.left`, so it skips it further.

5. At the `E.fold(...)` stage, because the value is `E.left`, the first function is executed, passing the error message contained within.

As a result, we get a response from the server saying "Item ID is missed" with a status code of `400`.

# Conclusion
Although the `fp-ts` library is quite popular and has detailed documentation, it was initially difficult to understand. Many thanks to [@souperman](https://stackoverflow.com/users/10345775/souperman) for his advice, without which I would not have been able to put together this example.

I will definitely try to make the most of the library in my next project, which I will be creating for myself and writing alone. The `fp-ts` syntax and approaches are too specific to be seamlessly integrated into projects where I work in a team, and the code should be as understandable as possible for any participant.

If I find any interesting patterns, I will write about them in a new article.