Interoperability with non functional code using fp-ts
Tags: functional, typescript
Published: 2019-02-12 12:16:54

---
title: Interoperability with non functional code using fp-ts
published: true
description:
tags: #functional, #typescript
---

Sometimes you are forced to interoperate with code not written in a functional style, let's see how to deal with it.

## Sentinels

Use case: an API that may fail and returns a special value of the codomain.

Example: `Array.prototype.findIndex`

Solution: `Option`

```ts
import { Option, none, some } from 'fp-ts/Option'

function findIndex<A>(
  as: Array<A>,
  predicate: (a: A) => boolean
): Option<number> {
  const index = as.findIndex(predicate)
  return index === -1 ? none : some(index)
}
```

## `undefined` and `null`

Use case: an API that may fail and returns `undefined` (or `null`).

Example: `Array.prototype.find`

Solution: `Option`, `fromNullable`

```ts
import { Option, fromNullable } from 'fp-ts/Option'

function find<A>(as: Array<A>, predicate: (a: A) => boolean): Option<A> {
  return fromNullable(as.find(predicate))
}
```

## Exceptions

Use case: an API that may throw.

Example: `JSON.parse`

Solution: `Either`, `tryCatch`

```ts
import { Either, tryCatch } from 'fp-ts/Either'

function parse(s: string): Either<Error, unknown> {
  return tryCatch(
    () => JSON.parse(s),
    (reason) => new Error(String(reason))
  )
}
```

## Random values

Use case: an API that returns a non deterministic value.

Example: `Math.random`

Solution: `IO`

```ts
import { IO } from 'fp-ts/IO'

const random: IO<number> = () => Math.random()
```

## Synchronous side effects

Use case: an API that reads and/or writes to a global state.

Example: `localStorage.getItem`

Solution: `IO`

```ts
import { Option, fromNullable } from 'fp-ts/Option'
import { IO } from 'fp-ts/IO'

function getItem(key: string): IO<Option<string>> {
  return () => fromNullable(localStorage.getItem(key))
}
```

Use case: an API that reads and/or writes to a global state and may throw.

Example: `readFileSync`

Solution: `IOEither`, `tryCatch`

```ts
import * as fs from 'fs'
import { IOEither, tryCatch } from 'fp-ts/IOEither'

function readFileSync(path: string): IOEither<Error, string> {
  return tryCatch(
    () => fs.readFileSync(path, 'utf8'),
    (reason) => new Error(String(reason))
  )
}
```

## Asynchronous side effects

Use case: an API that performs an asynchronous computation.

Example: reading from standard input

Solution: `Task`

```ts
import { createInterface } from 'readline'
import { Task } from 'fp-ts/Task'

const read: Task<string> = () =>
  new Promise<string>((resolve) => {
    const rl = createInterface({
      input: process.stdin,
      output: process.stdout
    })
    rl.question('', (answer) => {
      rl.close()
      resolve(answer)
    })
  })
```

Use case: an API that performs an asynchronous computation and may reject.

Example: `fetch`

Solution: `TaskEither`, `tryCatch`

```ts
import { TaskEither, tryCatch } from 'fp-ts/TaskEither'

function get(url: string): TaskEither<Error, string> {
  return tryCatch(
    () => fetch(url).then((res) => res.text()),
    (reason) => new Error(String(reason))
  )
}
```