Skip to content

Curry, Memo & Id

Three small but essential utilities for working with functions in a functional style.


import { curry } from "@oofp/core/curry";

Converts a multi-argument function into a sequence of single-argument functions (curried form). The resulting function can be partially applied one argument at a time.

const curry: <F extends (...args: any[]) => any>(fn: F) => Curried<F>;

The Curried<F> type recursively transforms each parameter into a separate function layer, so TypeScript tracks every intermediate type.

import { curry } from "@oofp/core/curry";
const add = (a: number, b: number) => a + b;
const curriedAdd = curry(add);
curriedAdd(1)(2); // 3
// Partial application
const increment = curriedAdd(1);
increment(5); // 6
increment(10); // 11
import { curry } from "@oofp/core/curry";
const formatName = (first: string, middle: string, last: string) =>
`${first} ${middle} ${last}`;
const curriedFormat = curry(formatName);
curriedFormat("John")("Paul")("Jones");
// "John Paul Jones"
// Partially apply for a family
const jones = curriedFormat("John")("Paul");
jones("Jones"); // "John Paul Jones"
jones("Smith"); // "John Paul Smith"
import { pipe } from "@oofp/core/pipe";
import { curry } from "@oofp/core/curry";
const multiply = curry((a: number, b: number) => a * b);
const add = curry((a: number, b: number) => a + b);
const result = pipe(
5,
multiply(2), // 10
add(3), // 13
);
// 13

import { uncurry } from "@oofp/core/curry";

The reverse of curry. Converts a curried function (a chain of single-argument functions) back into a multi-argument function.

const uncurry: <F extends (a: any) => any>(fn: F) => Uncurried<F>;
import { curry, uncurry } from "@oofp/core/curry";
const add = curry((a: number, b: number) => a + b);
// add: (a: number) => (b: number) => number
const addUncurried = uncurry(add);
// addUncurried: (a: number, b: number) => number
addUncurried(1, 2); // 3

This is useful when you need to pass a curried function to an API that expects a multi-argument callback:

import { curry, uncurry } from "@oofp/core/curry";
const combine = curry((separator: string, a: string, b: string) =>
[a, b].join(separator),
);
// Can't use curried form directly with some APIs
const joinWithComma = uncurry(combine(","));
joinWithComma("hello", "world"); // "hello,world"

import { evaluate } from "@oofp/core/curry";

Applies a single argument to a unary function. A simple utility for explicit function application.

const evaluate: <A, B>(fn: (a: A) => B, arg: A) => B;
import { evaluate } from "@oofp/core/curry";
const double = (n: number) => n * 2;
evaluate(double, 5); // 10

Useful when working with higher-order functions where you want to be explicit about application:

import { evaluate } from "@oofp/core/curry";
const transforms = [
(n: number) => n * 2,
(n: number) => n + 1,
(n: number) => n ** 2,
];
transforms.map((fn) => evaluate(fn, 5));
// [10, 6, 25]

import { memo } from "@oofp/core/memo";

Memoizes a unary function using a Map cache. Subsequent calls with the same argument return the cached result without re-executing the function.

const memo: <A, B>(fn: (a: A) => B) => (a: A) => B;
import { memo } from "@oofp/core/memo";
let callCount = 0;
const expensiveSquare = memo((n: number) => {
callCount++;
return n * n;
});
expensiveSquare(5); // 25, callCount = 1
expensiveSquare(5); // 25, callCount = 1 (cached)
expensiveSquare(3); // 9, callCount = 2
expensiveSquare(3); // 9, callCount = 2 (cached)
import { pipe } from "@oofp/core/pipe";
import { memo } from "@oofp/core/memo";
const parseConfig = memo((json: string) => {
console.log("Parsing...");
return JSON.parse(json);
});
const config1 = parseConfig('{"port": 3000}');
// Logs: "Parsing..."
// { port: 3000 }
const config2 = parseConfig('{"port": 3000}');
// No log — cached
// { port: 3000 }

The cache uses a Map, which means:

  • Primitives (strings, numbers, booleans) are compared by value.
  • Objects are compared by reference. Two different objects with the same shape will produce separate cache entries.
import { memo } from "@oofp/core/memo";
const stringify = memo((obj: { x: number }) => JSON.stringify(obj));
const a = { x: 1 };
stringify(a); // Computed
stringify(a); // Cached — same reference
stringify({ x: 1 }); // Computed again — different reference

For object arguments, consider using a serialization key or memoizing by a primitive identifier instead.


import { id } from "@oofp/core/id";

The identity function: returns its argument unchanged. Also exports the Identity<A> type.

type Identity<A> = (a: A) => A;
const id: <A>() => Identity<A>;
// Equivalent to: <A>() => (x: A) => A

Note that id is a factory — you call id() to get the identity function.

import { id } from "@oofp/core/id";
id<number>()(42); // 42
id<string>()("hello"); // "hello"

The identity function is useful as a default or no-op in conditional pipelines:

import { pipe } from "@oofp/core/pipe";
import { id } from "@oofp/core/id";
const transform = (shouldUpperCase: boolean) => (s: string) =>
pipe(
s,
shouldUpperCase ? (s) => s.toUpperCase() : id<string>(),
);
transform(true)("hello"); // "HELLO"
transform(false)("hello"); // "hello"
import { pipe } from "@oofp/core/pipe";
import { id } from "@oofp/core/id";
import * as E from "@oofp/core/either";
// Fold with identity on the right — extract the value as-is
const extractOrDefault = (fallback: string) =>
E.fold(() => fallback, id<string>());
pipe(E.right("success"), extractOrDefault("default"));
// "success"
pipe(E.left("error"), extractOrDefault("default"));
// "default"

Identity is essential in generic functional code where you need a “do nothing” function that satisfies type constraints:

import { id } from "@oofp/core/id";
const mapIf = <A>(condition: boolean, fn: (a: A) => A): (a: A) => A =>
condition ? fn : id<A>();
const double = (n: number) => n * 2;
mapIf(true, double)(5); // 10
mapIf(false, double)(5); // 5