fp-ts: Functional Programming in TypeScript
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
-
tsc hangs during compilation
cause Multiple versions of `fp-ts` are installed in your `node_modules` tree, confusing TypeScript's module resolution.fixRun `npm ls fp-ts` to identify duplicate versions. Use `npm dedupe` or manually adjust your `package.json` dependencies to ensure only a single `fp-ts` version is installed and deduped. -
ReferenceError: require is not defined
cause Attempting to use CommonJS `require()` syntax for `fp-ts` modules in an ECMAScript Module (ESM) environment (e.g., Node.js with `"type": "module"` or certain bundler configurations).fixMigrate your imports to ESM syntax: `import { pipe } from 'fp-ts/function';` instead of `const { pipe } = require('fp-ts/function');`. -
Property 'pipe' does not exist on type 'typeof import("/node_modules/fp-ts/lib/function")'cause Incorrect import path for `pipe` or other utilities, or a TypeScript configuration that doesn't correctly resolve module exports, especially if migrating from older versions or different module systems.fixEnsure you are using the correct import path `import { pipe } from 'fp-ts/function';`. Verify your `tsconfig.json` `moduleResolution` and `module` settings are compatible with modern Node.js or your bundler (e.g., `"moduleResolution": "bundler"` or `"node16"`). -
Type 'Foo' is not assignable to type 'Bar'. Argument of type 'Foo' is not assignable to parameter of type 'Bar'.
cause Type errors commonly arise when TypeScript's `strict` mode is disabled, leading to `fp-ts` functions inferring looser types than expected, or when `fp-ts` ADTs are not handled exhaustively or correctly.fixEnable `"strict": true` in your `tsconfig.json`. Ensure all possible cases of ADTs (e.g., `None`/`Some` for `Option`, `Left`/`Right` for `Either`) are handled using `match`, `fold`, or type guards.
Warnings
- breaking The `fp-ts` project is officially merging with the Effect-TS ecosystem. Effect-TS is positioned as the successor to `fp-ts v2` (effectively `fp-ts v3`), indicating a significant shift in the future roadmap and potentially requiring migration for new projects or major upgrades.
- gotcha Installing multiple versions of `fp-ts` in a single project is known to cause `tsc` to hang during compilation. Ensure only a single version is installed.
- gotcha fp-ts is designed for use with TypeScript's `strict` flag turned on. Developing without strict mode may lead to unexpected type behaviors or errors.
- deprecated The `chain` and `chainW` functions have been superseded by `flatMap` for most data types (e.g., `Option`, `Either`, `Task`, `Array`). While `chain` is still available, `flatMap` is the preferred and more idiomatic name.
- deprecated Functions like `mapLeft` (for `Either`, `These`, etc.) and `bimap` have been aliased or replaced by `mapError` and `mapBoth` respectively for consistency.
- breaking Version `2.13.0` was identified as a 'BROKEN RELEASE' due to issues with the `exports` field in `package.json`, which affected module resolution in `node12/nodenext` environments and could lead to import errors.
Install
-
npm install fp-ts -
yarn add fp-ts -
pnpm add fp-ts
Imports
- pipe
const { pipe } = require('fp-ts/function');import { pipe } from 'fp-ts/function'; - O (Option namespace)
import { Option } from 'fp-ts/Option';import * as O from 'fp-ts/Option';
- E (Either namespace)
import { Either } from 'fp-ts/Either';import * as E from 'fp-ts/Either';
- T (Task namespace)
const T = require('fp-ts/Task');import * as T from 'fp-ts/Task';
Quickstart
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();