Functional Programming Utilities
Collection of functional programming utilities with TypeScript
Code
// Currying
function curry<T extends any[], R>(
fn: (...args: T) => R
): (...args: T) => R {
const curried = (...args: any[]): any => {
__TOKEN_39__ (args.length >= fn.length) {
return fn(...args as T);
}
__TOKEN_41__ (...nextArgs: any[]) => curried(...args, ...nextArgs);
};
return curried as (...args: T) => R;
}
// Partial application
function partial<T extends any[], R>(
fn: (...args: T) => R,
...partialArgs: any[]
): (...args: any[]) => R {
__TOKEN_45__ (...remainingArgs: any[]) =>
fn(...partialArgs, ...remainingArgs) as R;
}
// Compose functions
function compose<T>(
...fns: ((arg: any) => any)[]
): (initialValue: T) => any {
__TOKEN_47__ (initialValue: T) =>
fns.reduceRight((value, fn) => fn(value), initialValue);
}
// Pipe functions (left to right composition)
function pipe<T>(
...fns: ((arg: any) => any)[]
): (initialValue: T) => any {
__TOKEN_49__ (initialValue: T) =>
fns.reduce((value, fn) => fn(value), initialValue);
}
// Memoization
function memoize<T __TOKEN_51__ (...args: any[]) => any>(
fn: T,
resolver?: (...args: Parameters<T>) => string
): T {
const cache = new Map<string, ReturnType<T>>();
__TOKEN_54__ ((...args: Parameters<T>): ReturnType<T> => {
const key = resolver ? resolver(...args) : JSON.stringify(args);
__TOKEN_56__ (cache.has(key)) {
return cache.get(key)!;
}
const result = fn(...args);
cache.set(key, result);
return result;
}) as T;
}
// Tap (for side effects in pipelines)
function tap<T>(effect: (value: T) => void): (value: T) => T {
__TOKEN_61__ (value: T) => {
effect(value);
return value;
};
}
// Maybe monad
class Maybe<T> {
private constructor(private value: T | null) {}
static just<T>(value: T): Maybe<T> {
return new Maybe(value);
}
static nothing<T>(): Maybe<T> {
return new Maybe<T>(null);
}
static from<T>(value: T | null | undefined): Maybe<T> {
return value == null ? Maybe.nothing() : Maybe.just(value);
}
map<U>(fn: (value: T) => U): Maybe<U> {
return this.value == null ? Maybe.nothing<U>() : Maybe.just(fn(this.value));
}
flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U> {
return this.value == null ? Maybe.nothing<U>() : fn(this.value);
}
getOrElse(defaultValue: T): T {
return this.value == null ? defaultValue : this.value;
}
isJust(): boolean {
return this.value != null;
}
isNothing(): boolean {
return this.value == null;
}
}
// Either monad
type Either<L, R> = Left<L> | Right<R>;
class Left<L> {
readonly tag = 'Left' as const;
constructor(readonly value: L) {}
map<R>(_fn: (value: any) => R): Either<L, R> {
return this;
}
flatMap<R>(_fn: (value: any) => Either<L, R>): Either<L, R> {
return this;
}
}
class Right<R> {
readonly tag = 'Right' as const;
constructor(readonly value: R) {}
map<S>(fn: (value: R) => S): Either<any, S> {
return new Right(fn(this.value));
}
flatMap<L, S>(fn: (value: R) => Either<L, S>): Either<L, S> {
return fn(this.value);
}
}
// Utility functions for Either
const left = <L, R>(value: L): Either<L, R> => new Left(value);
const right = <L, R>(value: R): Either<L, R> => new Right(value);
// Lazy evaluation
class Lazy<T> {
private fn: () => T;
private cachedValue?: T;
private evaluated = false;
constructor(fn: () => T) {
this.fn = fn;
}
static of<T>(value: T): Lazy<T> {
return new Lazy(() => value);
}
map<U>(fn: (value: T) => U): Lazy<U> {
return new Lazy(() => fn(this.value));
}
flatMap<U>(fn: (value: T) => Lazy<U>): Lazy<U> {
return new Lazy(() => fn(this.value).value);
}
get value(): T {
__TOKEN_125__ (!this.evaluated) {
this.cachedValue = this.fn();
this.evaluated = true;
}
return this.cachedValue!;
}
}
// Higher-order functions
function not<T>(fn: (value: T) => boolean): (value: T) => boolean {
__TOKEN_133__ (value: T) => !fn(value);
}
function and<T>(...fns: ((value: T) => boolean)[]): (value: T) => boolean {
__TOKEN_135__ (value: T) => fns.every(fn => fn(value));
}
function or<T>(...fns: ((value: T) => boolean)[]): (value: T) => boolean {
__TOKEN_137__ (value: T) => fns.some(fn => fn(value));
}
// Usage examples
// Currying
const add = (a: number, b: number) => a + b;
const curriedAdd = curry(add);
const add5 = curriedAdd(5);
console.log(add5(3)); // 8
// Composition
const double = (x: number) => x * 2;
const square = (x: number) => x * x;
const doubleThenSquare = compose(square, double);
console.log(doubleThenSquare(3)); // 36
const squareThenDouble = pipe(double, square);
console.log(squareThenDouble(3)); // 36
// Memoization
const expensiveCalculation = memoize((a: number, b: number) => {
console.log('Calculating...');
return a + b;
});
console.log(expensiveCalculation(1, 2)); // Calculating... 3
console.log(expensiveCalculation(1, 2)); // 3 (cached)
// Maybe monad
const getUser = (id: number): Maybe<{ name: string; age: number }> => {
return id > 0 ? Maybe.just({ name: 'John', age: 30 }) : Maybe.nothing();
};
const userName = getUser(1)
.map(user => user.name.toUpperCase())
.getOrElse('Unknown');
// Either monad
function divideEither(a: number, b: number): Either<string, number> {
return b === 0 ? left('Division by zero') : right(a / b);
}
const result = divideEither(10, 2)
.map(x => x * 3)
.flatMap(x => divideEither(x, 5));
// Lazy evaluation
const lazyValue = Lazy.__TOKEN_154__(() => {
console.log('Computing...');
return 42;
});
console.log(lazyValue.value); // Computing... 42
console.log(lazyValue.value); // 42 (cached)
// Pipeline with tap
const processData = pipe(
(x: number) => x * 2,
tap(x => console.log('After doubling:', x)),
(x: number) => x + 10,
tap(x => console.log('After adding:', x))
);
console.log(processData(5)); // 20