fp-ts: Functional Programming in TypeScript

2.16.11 · maintenance · verified Sun Apr 19

fp-ts is a TypeScript library providing tools for typed functional programming, including popular algebraic data types like `Option`, `Either`, `IO`, and `Task`, alongside type classes such as `Functor`, `Applicative`, and `Monad`. It uniquely implements Higher Kinded Types to enable robust functional patterns within TypeScript's type system. The current stable version is 2.16.11, last published approximately 8 months ago as of the current date. While actively maintained, recent announcements indicate that the `fp-ts` project is officially merging with the Effect-TS ecosystem, with Effect-TS being positioned as the successor, akin to `fp-ts v3`. This transition implies a future shift in development focus towards the Effect-TS project, offering enhanced capabilities and support for new users.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates fetching data, parsing JSON, and safely accessing optional properties using `TaskEither`, `Either`, `Option`, and the `pipe` function for functional composition and error handling. It shows how to chain operations that can fail (HTTP request, JSON parsing) and gracefully handle missing values.

import { pipe } from 'fp-ts/function';
import * as O from 'fp-ts/Option';
import * as E from 'fp-ts/Either';
import * as TE from 'fp-ts/TaskEither';

interface User {
  id: number;
  name: string;
  email?: string;
}

const fetchUser = (userId: number): TE.TaskEither<Error, string> =>
  TE.tryCatch(
    () =>
      fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then(
        (res) => {
          if (!res.ok) {
            throw new Error(`HTTP error! status: ${res.status}`);
          }
          return res.text();
        },
      ),
    (reason) => new Error(String(reason)),
  );

const parseJson = (s: string): E.Either<Error, User> =>
  E.tryCatch(
    () => JSON.parse(s) as User,
    (reason) => new Error(`Failed to parse JSON: ${String(reason)}`),
  );

const getUserEmail = (user: User): O.Option<string> =>
  O.fromNullable(user.email);

// Example usage:
const run = async () => {
  console.log('Fetching user 1...');
  const result = await pipe(
    fetchUser(1), // Fetch user with ID 1
    TE.flatMapEither(parseJson), // Parse the response as JSON, converting TaskEither<Error, string> to TaskEither<Error, User>
    TE.map(getUserEmail), // Map the User to an Option<string> (email)
    TE.match(
      (error) => `Error fetching user 1: ${error.message}`,
      (emailOption) =>
        pipe(
          emailOption,
          O.match(
            () => 'User 1 email not found.',
            (email) => `User 1 email: ${email}`,
          ),
        ),
    ),
  )();
  console.log(result);

  console.log('\nAttempting to fetch non-existent user 999...');
  const invalidResult = await pipe(
    fetchUser(999), // Fetch a non-existent user, likely resulting in an HTTP error
    TE.flatMapEither(parseJson), // This step will likely be skipped or fail due to the upstream error
    TE.map(getUserEmail),
    TE.match(
      (error) => `Error fetching user 999: ${error.message}`,
      (emailOption) =>
        pipe(
          emailOption,
          O.match(
            () => 'User 999 email not found.',
            (email) => `User 999 email: ${email}`,
          ),
        ),
    ),
  )();
  console.log(invalidResult);
};

run();

view raw JSON →