fp-ts-cheatsheet

:memo: fp-ts cheatsheet

More detailed explanations can be found in the Details document.

  1. Imports
  2. Pipe and Flow
  3. Option
  4. Either
  5. TaskEither
  6. Array and ReadonlyArray
  7. Do-notation

Imports

import { either, option } from 'fp-ts'; // import modules from root
import { flow, pipe } from 'fp-ts/functions'; // import functions from functions
import { Either } from 'fp-ts/Either'; // import types from their module

// rename common long module names
import { readerTaskEither as rte } from 'fp-ts';

Pipe and Flow

const value = 'value';

// Imperative style
const value1 = addSthg(value);
const value2 = doSthgElse(value1);
const finalValue = doFinalSthg(value2);
// Or maybe inline
const finalValue = doFinalSthg(doSthgElse(addSthg(value)));

// With pipe
const finalValue = pipe(value, addSthg, doSthgElse, doFinalSthg);

// With flow
const transformations = flow(addSthg, doSthgElse, doFinalSthg);
const finalValue = transformations(value);

Option

Create an Option<T>

// Build an Option
option.none;
option.some('value');

// Build from a value
option.fromNullable(null); // => option.none
option.fromNullable('value'); // => option.some('value')

// Build from a predicate
const isEven = number => number % 2 === 0;

option.fromPredicate(isEven)(3); // => option.none
option.fromPredicate(isEven)(4); // => option.some(4)

// Convert from another type (eg. Either)
const leftEither = either.left('whatever');
const rightEither = either.right('value');

option.fromEither(leftEither); // => option.none
option.fromEither(rightEither); // => option.some('value')

Extract inner value

const noneValue = option.none;
const someValue = option.of(42);

// Convert Option<T> to T | undefined
option.toUndefined(noneValue); // => undefined
option.toUndefined(someValue); // => 42

// Convert Option<T> to T | null
option.toNullable(noneValue); // => null
option.toNullable(someValue); // => 42

// Convert Option<T> with a default value
option.getOrElse(() => 0)(noneValue); // => 0
option.getOrElse(() => 0)(someValue); // => 42

// Convert Option<T> to T | U with a default value of type U
option.getOrElseW(() => 'default')(noneValue); // => 'default': number | string

// Apply a different function on None/Some(...)
const doubleOrZero = option.match(
  () => 0, // none case
  (n: number) => 2 * n // some case
);

doubleOrZero(noneValue); // => 0
doubleOrZero(someValue); // => 84

// Pro-tip: option.match is short for the following:
const doubleOfZeroBis = flow(
  option.map((n: number) => 2 * n), // some case
  option.getOrElse(() => 0) // none case
);

Either

Create an Either<E, A>

// Build an Either
const leftValue = either.left('value');
const rightValue = either.right('value');

// Build from a value
either.fromNullable('value was nullish')(null); // => either.left('value was nullish')
either.fromNullable('value was nullish')('value'); // => either.right('value')

// Build from a predicate
const isEven = (num: number) => num % 2 === 0;

const eitherBuilder = either.fromPredicate(
  isEven,
  number => `${number} is an odd number`
);

eitherBuilder(3); // => either.left('3 is an odd number')
eitherBuilder(4); // => either.right(4)

// Convert from another type (eg. Option)
const noneValue = option.none;
const someValue = option.some('value');

either.fromOption(() => 'value was nullish')(noneValue); // => either.left('value was nullish')
either.fromOption(() => 'value was nullish')(someValue); // => either.right('value')

Extract inner value

const leftValue = either.left("Division by Zero!");
const rightValue = either.right(10);

// Convert Either<E, A> to A with a default value
either.getOrElse(() => 0)(leftValue); // => 0
either.getOrElse(() => 0)(rightValue); // => 10

// Apply a different function on Left(...)/Right(...)
const doubleOrZero = either.match(
  (error: string) => {
    console.log(`The error was ${error}`);
    return 0;
  },
  (n: number) => 2 * n,
);

doubleOrZero(leftValue); // => 0 - also logs "The error was Division by Zero!"
doubleOrZero(rightValue); // => 20

// Pro-tip: either.match is short for the following:
const doubleOrZeroBis = flow(
  either.map((n: number) => 2 * n),
  either.getOrElse((error: string) => {
    console.log(`The error was ${error}`);
    return 0;
  });
);

TaskEither

Create a TaskEither<E, A>

// Build a TaskEither
const leftValue = taskEither.left('value');
const rightValue = taskEither.right('value');

// The taskEither module also provides similar builder functions
// to either, namely: `fromNullable`, `fromOption`, `fromPredicate`...

// Convert from Either
const eitherValue = either.right(42);

taskEither.fromEither(eitherValue); // => taskEither.right(42)

// Build from an async function
const asyncIsEven = async (n: number) => {
  if (!isEven(n)) {
    throw new Error(`${n} is odd`);
  }

  return n;
};

const buildTaskEither = (n: number) =>
  taskEither.tryCatch(
    () => asyncIsEven(n),
    (error: Error) => error.message
  );

buildTaskEither(3); // => taskEither.left('3 is odd')
buildTaskEither(4); // => taskEither.right(4)

Extract inner value

// Simple case: an infaillible Task
const someTask = task.of(42);

// Invoking a Task returns the underlying Promise
await someTask(); // => 42

// TaskEither
const someTaskEither = taskEither.right(42);
const eitherValue = await someTaskEither(); // => either.right(42)
either.getOrElse(() => 0)(eitherValue); // => 42

// Or more directly
const infaillibleTask = taskEither.getOrElse(() => 0)(someTaskEither); // => task.of(42)
await infaillibleTask(); // => 42

ReadonlyArray

Basic manipulation

const someArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

// filter and map
const result = pipe(
  someArray,
  readonlyArray.filter(isEven),
  readonlyArray.map(square)
); // => [0, 4, 16, 36, 64]

// or in one go with filterMap
const result = pipe(
  someArray,
  readonlyArray.filterMap(
    flow(option.fromPredicate(isEven), option.map(square))
  )
);

Sort elements with Ord

const strings = ['zyx', 'abc', 'klm'];

// basic sort
const sortedStrings = pipe(strings, readonlyArray.sort(string.Ord)); // => ['abc', 'klm', 'zyx']

// reverse sort
const reverseSortedStrings = pipe(
  strings,
  readonlyArray.sort(ord.reverse(string.Ord))
); // => ['zyx', 'klm', 'abc']

// sort Option
const optionalNumbers = [option.some(1337), option.none, option.some(42)];

const sortedNums = pipe(nums, readonlyArray.sort(option.getOrd(number.Ord))); // => [option.none, option.some(42), option.some(1337)]

// sort complex objects with different rules
type User = {
  name: string;
  age: Option<number>;
};

const byName = pipe(
  string.Ord,
  ord.contramap((user: User) => user.name)
);

const byAge = pipe(
  option.getOrd(number.Ord),
  ord.contramap((user: User) => user.age)
);

const sortUsers = readonlyArray.sortBy([byAge, byName]); // will sort an array of users by age first, then by name

Do-notation

import { readerTaskEither as rte } from 'fp-ts';
import { pipe } from 'fp-ts/function';
import { ReaderTaskEither } from 'fp-ts/ReaderTaskEither';

declare const foo: ReaderTaskEither<R1, E1, A1>;
declare const bar: ReaderTaskEither<R2, E2, A2>;
declare const baz: (props: {
  foo: A1;
  bar: A2;
}) => ReaderTaskEither<R3, E3, A3>;
declare const quux: (props: {
  foo: A1;
  bar: A2;
  baz: A3;
}) => ReaderTask<R4, A4>;
declare const transform: (props: { foo: A1; bar: A2; baz: A3 }) => B;

pipe(
  rte.Do,
  rte.apS('foo', foo),
  rte.apSW('bar', bar),
  rte.bindW('baz', baz),
  rte.chainFirstReaderTaskKW(quux),
  rte.map(transform)
);
// => ReaderTaskEither<
//      R1 & R2 & R3 & R4,
//      E1 | E2 | E3,
//      B,
//    >