Type classes in fp-ts provide generic operations that work across different data types. They enable higher-level abstractions and code reuse through polymorphism, similar to interfaces but more powerful.
Map functions over wrapped values, preserving structure.
interface Functor<F> {
readonly map: <A, B>(fa: HKT<F, A>, f: (a: A) => B) => HKT<F, B>;
}
interface Functor1<F extends URIS> {
readonly map: <A, B>(fa: Kind<F, A>, f: (a: A) => B) => Kind<F, B>;
}
interface Functor2<F extends URIS2> {
readonly map: <L, A, B>(fa: Kind2<F, L, A>, f: (a: A) => B) => Kind2<F, L, B>;
}Usage Examples:
import { map } from "fp-ts/lib/Option";
import { some, none } from "fp-ts/lib/Option";
// Map over Option
const doubled = map((n: number) => n * 2)(some(21)); // Some(42)
const stillNone = map((n: number) => n * 2)(none); // NoneApply wrapped functions to wrapped values.
interface Apply<F> extends Functor<F> {
readonly ap: <A, B>(fab: HKT<F, (a: A) => B>, fa: HKT<F, A>) => HKT<F, B>;
}
interface Apply1<F extends URIS> extends Functor1<F> {
readonly ap: <A, B>(fab: Kind<F, (a: A) => B>, fa: Kind<F, A>) => Kind<F, B>;
}
interface Apply2<F extends URIS2> extends Functor2<F> {
readonly ap: <L, A, B>(fab: Kind2<F, L, (a: A) => B>, fa: Kind2<F, L, A>) => Kind2<F, L, B>;
}Lift pure values into wrapped context, extending Apply.
interface Applicative<F> extends Apply<F> {
readonly of: <A>(a: A) => HKT<F, A>;
}
interface Applicative1<F extends URIS> extends Apply1<F> {
readonly of: <A>(a: A) => Kind<F, A>;
}
interface Applicative2<F extends URIS2> extends Apply2<F> {
readonly of: <L, A>(a: A) => Kind2<F, L, A>;
}Usage Examples:
import { sequenceS } from "fp-ts/lib/Apply";
import { option } from "fp-ts/lib/Option";
import { some, none } from "fp-ts/lib/Option";
// Combine multiple Options
const result = sequenceS(option)({
name: some("Alice"),
age: some(30),
email: some("alice@example.com")
});
// Some({ name: "Alice", age: 30, email: "alice@example.com" })
// If any is None, result is None
const failed = sequenceS(option)({
name: some("Bob"),
age: none,
email: some("bob@example.com")
});
// NoneSequential composition of wrapped computations (also known as flatMap).
interface Chain<F> extends Apply<F> {
readonly chain: <A, B>(fa: HKT<F, A>, f: (a: A) => HKT<F, B>) => HKT<F, B>;
}
interface Chain1<F extends URIS> extends Apply1<F> {
readonly chain: <A, B>(fa: Kind<F, A>, f: (a: A) => Kind<F, B>) => Kind<F, B>;
}
interface Chain2<F extends URIS2> extends Apply2<F> {
readonly chain: <L, A, B>(fa: Kind2<F, L, A>, f: (a: A) => Kind2<F, L, B>) => Kind2<F, L, B>;
}Combines Applicative and Chain, enabling full monadic operations.
interface Monad<F> extends Applicative<F>, Chain<F> {}
interface Monad1<F extends URIS> extends Applicative1<F>, Chain1<F> {}
interface Monad2<F extends URIS2> extends Applicative2<F>, Chain2<F> {}Usage Examples:
import { chain, of } from "fp-ts/lib/Option";
import { some, none } from "fp-ts/lib/Option";
// Chain optional computations
const divide = (n: number) => (d: number) =>
d === 0 ? none : some(n / d);
const result1 = chain(divide(10))(some(2)); // Some(5)
const result2 = chain(divide(10))(some(0)); // None
const result3 = chain(divide(10))(none); // NoneFold (reduce) structures to single values.
interface Foldable<F> {
readonly reduce: <A, B>(fa: HKT<F, A>, b: B, f: (b: B, a: A) => B) => B;
readonly foldMap: <M>(M: Monoid<M>) => <A>(fa: HKT<F, A>, f: (a: A) => M) => M;
readonly reduceRight: <A, B>(fa: HKT<F, A>, b: B, f: (a: A, b: B) => B) => B;
}
interface Foldable1<F extends URIS> {
readonly reduce: <A, B>(fa: Kind<F, A>, b: B, f: (b: B, a: A) => B) => B;
readonly foldMap: <M>(M: Monoid<M>) => <A>(fa: Kind<F, A>, f: (a: A) => M) => M;
readonly reduceRight: <A, B>(fa: Kind<F, A>, b: B, f: (a: A, b: B) => B) => B;
}Usage Examples:
import { reduce, foldMap } from "fp-ts/lib/Array";
import { monoidSum } from "fp-ts/lib/Monoid";
// Reduce array to sum
const numbers = [1, 2, 3, 4, 5];
const sum = reduce(0, (acc: number, n: number) => acc + n)(numbers); // 15
// Fold with monoid
const total = foldMap(monoidSum)((n: number) => n)(numbers); // 15Traverse structures, applying effects while preserving shape.
interface Traversable<F> extends Functor<F>, Foldable<F> {
readonly traverse: <G>(G: Applicative<G>) => <A, B>(ta: HKT<F, A>, f: (a: A) => HKT<G, B>) => HKT<G, HKT<F, B>>;
readonly sequence: <G>(G: Applicative<G>) => <A>(tga: HKT<F, HKT<G, A>>) => HKT<G, HKT<F, A>>;
}Usage Examples:
import { traverse } from "fp-ts/lib/Array";
import { option } from "fp-ts/lib/Option";
import { some, none } from "fp-ts/lib/Option";
// Transform array of numbers to array of Options, then sequence
const parseInts = (strings: string[]) =>
traverse(option)(strings, (s: string) => {
const n = parseInt(s, 10);
return isNaN(n) ? none : some(n);
});
const valid = parseInts(["1", "2", "3"]); // Some([1, 2, 3])
const invalid = parseInts(["1", "a", "3"]); // NoneAssociative binary operation for combining values.
interface Semigroup<A> {
readonly concat: (x: A, y: A) => A;
}Usage Examples:
import { semigroupSum, semigroupString } from "fp-ts/lib/Semigroup";
// Numeric addition
const sum = semigroupSum.concat(5, 10); // 15
// String concatenation
const greeting = semigroupString.concat("Hello", " World"); // "Hello World"
// Custom semigroup
const semigroupMax: Semigroup<number> = {
concat: (x, y) => Math.max(x, y)
};
const maximum = semigroupMax.concat(3, 7); // 7Semigroup with identity element.
interface Monoid<A> extends Semigroup<A> {
readonly empty: A;
}Usage Examples:
import { monoidSum, monoidString, fold } from "fp-ts/lib/Monoid";
// Identity elements
const zeroSum = monoidSum.empty; // 0
const emptyString = monoidString.empty; // ""
// Fold array with monoid
const numbers = [1, 2, 3, 4, 5];
const total = fold(monoidSum)(numbers); // 15
const words = ["Hello", " ", "World"];
const sentence = fold(monoidString)(words); // "Hello World"Equality testing type class.
interface Eq<A> {
readonly equals: (x: A, y: A) => boolean;
}Usage Examples:
import { eqNumber, eqString, contramap } from "fp-ts/lib/Eq";
// Basic equality
const isEqual = eqNumber.equals(5, 5); // true
const notEqual = eqString.equals("a", "b"); // false
// Derive equality for complex types
interface Person {
name: string;
age: number;
}
const eqPerson: Eq<Person> = {
equals: (x, y) => x.name === y.name && x.age === y.age
};
// Use contramap to derive from existing Eq
const eqPersonByName = contramap((p: Person) => p.name)(eqString);Ordering/comparison type class.
interface Ord<A> extends Eq<A> {
readonly compare: (x: A, y: A) => Ordering;
}
type Ordering = -1 | 0 | 1;Usage Examples:
import { ordNumber, ordString, min, max, sort } from "fp-ts/lib/Ord";
// Comparison
const comparison = ordNumber.compare(5, 10); // -1 (less than)
// Min/max
const minimum = min(ordNumber)(3, 7); // 3
const maximum = max(ordString)("apple", "banana"); // "banana"
// Sort array
const numbers = [3, 1, 4, 1, 5];
const sorted = sort(ordNumber)(numbers); // [1, 1, 3, 4, 5]String representation type class.
interface Show<A> {
readonly show: (a: A) => string;
}Usage Examples:
import { showNumber, showString, showBoolean } from "fp-ts/lib/Show";
// Basic show instances
const numStr = showNumber.show(42); // "42"
const strStr = showString.show("hello"); // "hello"
const boolStr = showBoolean.show(true); // "true"
// Custom show instance
interface Person {
name: string;
age: number;
}
const showPerson: Show<Person> = {
show: (p) => `${p.name} (${p.age})`
};
const personStr = showPerson.show({ name: "Alice", age: 30 }); // "Alice (30)"Choice between alternatives with identity.
interface Alternative<F> extends Applicative<F>, Plus<F> {}
interface Plus<F> extends Alt<F> {
readonly zero: <A>() => HKT<F, A>;
}
interface Alt<F> extends Functor<F> {
readonly alt: <A>(fx: HKT<F, A>, fy: Lazy<HKT<F, A>>) => HKT<F, A>;
}Usage Examples:
import { alt } from "fp-ts/lib/Option";
import { some, none } from "fp-ts/lib/Option";
// Alternative choices
const first = alt(() => some("fallback"))(some("primary")); // Some("primary")
const second = alt(() => some("fallback"))(none); // Some("fallback")
const third = alt(() => none)(none); // NoneGeneric operations that work with any type class instance.
/**
* Sequence array of wrapped values
* @param A - Applicative instance
* @returns Function that sequences array
*/
function sequenceArray<F extends URIS>(A: Applicative1<F>): <A>(arr: Array<Kind<F, A>>) => Kind<F, Array<A>>;
/**
* Traverse array with effect
* @param A - Applicative instance
* @returns Function that traverses array
*/
function traverseArray<F extends URIS>(A: Applicative1<F>): <A, B>(f: (a: A) => Kind<F, B>) => (arr: Array<A>) => Kind<F, Array<B>>;
/**
* Replicate wrapped value n times
* @param A - Applicative instance
* @returns Function that replicates value
*/
function replicateA<F extends URIS>(A: Applicative1<F>): <A>(n: number, fa: Kind<F, A>) => Kind<F, Array<A>>;