Functional programming in typescript using fp-ts: ReaderTaskEither
Tags: functional, typescript
Published: 2020-09-10 07:11:10

This article will demonstrate how to leverage the [fp-ts](https://gcanti.github.io/fp-ts/) library to make async requests in typescript using functional programming.

Say we want to make an HTTP request. From that we know at least the following:

1. We need an HTTP client to make the call.
2. We need to handle a `Promise`.
3. We need to anticipate errors.

Let's reiterate those three points, this time in terms of what they signify.

1. A dependency.
2. An asynchronous call.
3. Something that can go wrong.

In functional programming we use certain data types to deal with these types of complexity. In our case specifically:

1. [Reader](https://gcanti.github.io/fp-ts/modules/Either.ts.html) - for dependency injection.
2. [Task](https://gcanti.github.io/fp-ts/modules/Task.ts.html) - for asynchronous things.
3. [Either](https://gcanti.github.io/fp-ts/modules/Either.ts.html) - for things that can go wrong.

We could compose these monads ourselves, but fp-ts provides us with some useful compositions out of the box, like the [ReaderTaskEither](https://gcanti.github.io/fp-ts/modules/ReaderTaskEither.ts.html). That is a Reader containing a Task containing an Either. Let's go over them and see what we can do with their composition. 

<sub>Note: The code snippets containing a `describe` block can be executed with [Jest](https://jestjs.io/) or [Jasmine](https://jasmine.github.io/).</sub>

## 1. Reader

`Reader<A, B>` is just a function from `A` to `B`. But having that type alias is very useful: it means we want to do dependency injection.

``` typescript
import * as R from 'fp-ts/lib/Reader'

describe('Reader', () => {
  it('should just be a function', () => {
    const myReader: R.Reader<number, string> = (a: number) => String(a)
    expect(myReader(15)).toBe('15')
  })
}
```

If you know dependency injection then you are probably used to injecting class instances into constructors to build up a dependency tree, after which you send in the parameters from a top level function that will then traverse the tree. This means dependencies get injected first and function parameters get passed second. In functional programming it goes the other way around. We first inject the parameters in order to create a function of external dependencies. Only then do we send in those dependencies to execute the pipe. Let's see how this works in practice:

<sub>Note that the types `HttpClient` and `Response` are hypothetical.</sub>

```typescript
// imperative example
class MyController {
  private HttpClient client
  constructor(HttpClient client) {
    this.client = client
  }

  async fetch(path: string): Response {
    return await this.client.get(path)
  }
}

// ...

const controller = new MyController(new HttpClient()) // inject dependencies
const res: Response = await controller.fetch('events') // pass parameters and execute
```

```typescript
// functional example
import * as R from 'fp-ts/Reader'

// ...

const fetch = (url: string) =>
      R.asks((client: HttpClient) => client.get(url))
const fetchEvents = fetch('events') // pass parameters
const res: Response = await fetchEvents(new HttpClient() // inject depdencies and execute
```

`asks<A, B>(f)` will return a reader that will provide `A` as a parameter to `f`, which in turn must return a `B`.

You can find a more detailed explanation of the Reader monad in this article: [Getting started with fp-ts: Reader](https://dev.to/gcanti/getting-started-with-fp-ts-reader-1ie5).

> ### A note on pipelines
> 
> That last example was straightforward, but operations like that involve more logic before and after our `fetch` function. In functional programming we use pipelines to compose functions for more complicated operations. With fp-ts we can use [pipe](https://gcanti.github.io/fp-ts/modules/function.ts.html#pipe) or [flow](https://gcanti.github.io/fp-ts/modules/function.ts.html#flow) for that. We can rewrite `fetchEvents` as follows:
> 
> ``` typescript
> // with pipe
> const fetchEvents = pipe('events', fetch)
> 
> // with flow
> const fetchEvents = flow(fetch)('events')
> ```
> With `pipe` and `flow` we can easily put more functions in:
> - Put them in front of `fetch` for things we want to do to the > `events` parameter before calling fetch.
> - Put them after `fetch` for things we want to do to the result of calling `fetch`.

## 2. Task

A `Task` is a function that returns a `Promise`. That way we can defer execution of the `Promise` until we call upon the `Task`.

``` typescript
import * as T from 'fp-ts/lib/Task'

describe('Task', () => {
  it('of should work', async () => {
    // Arrange
    const mytask = T.of('abc')
    // Act
    const result = await mytask()
    // Assert
    expect(result).toEqual('abc')
  })

  it('should work with promise', async () => {
    // Arrange
    const mytask: T.Task<string> = () => Promise.resolve('def')
    // Act
    const result = await mytask()
    // Assert
    expect(result).toEqual('def')
  })
})
```

## 3. Either

`Either` is a way of anticipating failures. It has two instances; `left` and `right` - `left` indicating a failure and `right` indicating that everything is OK.

``` typescript
import * as E from 'fp-ts/lib/Either'

describe('Either', () => {
  it('of should create a right', () => {
    // Act
    const myEither = E.of('abc')
    // Assert
    expect(myEither).toEqual(E.right('abc'))
  })
})
```

`Either` has some helper constructors, like `fromNullable` which returns a left if the parameter is `null` or `undefined`.

``` typescript
import * as E from 'fp-ts/lib/Either'

describe('Either.fromNullable', () => {
  const errorMessage = 'input was null or undefined'
  const toEither = E.fromNullable(errorMessage)

  it('should return right with string', async () => {
    // Arrange
    const expected = 'abc'
    // Act
    const myEither = toEither(expected)
    // Assert
    expect(myEither).toEqual(E.right(expected))
  })

  it('should return left with undefined', async () => {
    // Act
    const myEither = toEither(undefined)
    // Assert
    expect(myEither).toEqual(E.left(errorMessage))
  })
})
```

## 4. Task + Either = TaskEither

`TaskEither` is an `Either` inside of a `Task`, meaning it's an asynchronous operation that can fail.  It provides us with the useful function `tryCatch` that takes two parameters: The first is a function that returns a promise, the second is function that maps the rejected result to something that ends up in a `left` - we'll just use the `String` constructor for that in this example:

``` typescript
import * as TE from 'fp-ts/lib/TaskEither'
import * as E from 'fp-ts/lib/Either'

describe('TaskEither', () => {
  it('should work with tryCatch with resolved promise', async () => {
    // Arrange
    const expected = 135345
    // Act
    const mytask = TE.tryCatch(() => Promise.resolve(expected), String)
    const result = await mytask()
    // Assert
    expect(result).toEqual(E.right(expected))
  })

  it('should work with tryCatch with rejected promise', async () => {
    // Arrange
    const expected = 'Dummy error'
    // Act
    const mytask = TE.tryCatch(() => Promise.reject(expected), String)
    const result = await mytask()
    // Assert
    expect(result).toEqual(E.left(expected))
  })
})
```

[Click here](https://rlee.dev/practical-guide-to-fp-ts-part-3) to go to an extensive article about Task, Either and TaskEither.

## 5. Reader + Task + Either = ReaderTaskEither

Now let's put the parts together to create a functional data structure that we can use to make an HTTP request. Let's begin with a naive approach:

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

const client = new HttpClient()
const myTryCatch = TE.tryCatch(() => client.get('events'), String)
```

We refactor to parameterize the client and the url, using [currying](https://medium.com/@harouny/currying-in-javascript-arrow-function-sequence-2a510441215a).

``` typescript
const myTryCatch = (url: String) => (client: Client) => TE.tryCatch(() => client.get(url), String)
```

Let's declare the type of this function:

``` typescript
type MyTryCatch = (url: string) => (client: HttpClient) => TE.TaskEither<string, Response>
const myTryCatch: MyTryCatch = (url) => (client) => TE.tryCatch(() => client.get(url), String)
```

Now we are going to rewrite the type using `Reader`:

``` typescript
type MyTryCatch = (url: string) => Reader<HttpClient, TE.TaskEither<string, Response>>
```

And as you've probably guessed, a `Reader` returning a `TaskEither` is a `ReaderTaskEither`:

``` typescript
import * as RTE from 'fp-ts/lib/ReaderTaskEither'

type MyTryCatch = (url: string) => RTE.ReaderTaskEither<HttpClient, string, Response>
```
This is a single type that tells you all you need to know about the logic it encapsulates: 
- It has a dependency on `HttpClient`
- The error gets formatted to a `string`
- A successful execution will result in a `Response`

## Seeing it in action

The nice thing about `Reader` is that we can obtain the dependency anywhere in a pipe using `asks`, which we've discussed earlier in this article. This is how it would work:

``` typescript
import * as RTE from 'fp-ts/lib/ReaderTaskEither'
import * as TE from 'fp-ts/lib/TaskEither'
import * as R from 'fp-ts/lib/Reader'

// ...

pipe(
  // ...functions leading up to a ReaderTaskEither containing a userId
  RTE.chain((userId) =>
    R.asks((client: HttpClient) => TE.tryCatch(() => client.get(`user/${userId}`), String))
  )
  // ...functions handling the response
)
```

Typically you would extract this function to make the pipe more readable.

``` typescript
pipe(
  // ...
  RTE.chain(fetchUser)
  // ...
)

// ...

const fetchUser = (userId: string) => (client: HttpClient) => TE.tryCatch(() => client.get(`user/${userId}`), String))
```

> If `chain` is unfamiliar to you, it takes a function that maps whatever the monad is holding and returns a monad of something else. In this example it is assumed we start with a `ReaderTaskEither` of a `string` userId and we chain it to a `ReaderTaskEither` of a `Response`. For more information about monads and `chain` in fp-ts checkout [Getting started with fp-ts: Monad](https://dev.to/gcanti/getting-started-with-fp-ts-monad-6k)

## Conclusion

Doing functional programming in typescript is not required and therefore a discipline. Nothing is forcing you to use a `ReaderTaskEither` for asynchronous operations, but the reward is a function that you can use in any pipe that by its signature is honest about what it does. It also makes for excellent testability: by using `Reader` we don't have to worry about instantiating a class that may or may not require more dependencies than we care about for a given test.

Checkout the [official documentation](https://gcanti.github.io/fp-ts/modules/ReaderTaskEither.ts.html#chaineitherk) for more information.