Skip to content

Repository files navigation

fpure

Coverage Status

Pure, curried, functional programming utilities for TypeScript.

Inspired by Ramda and Sanctuary

Install

npm install fpure

Usage Example

import { compose, add, map, filter, gt } from 'fpure';

compose(
  filter(gt(3)), 
  map(add(1))
)([1, 2, 3, 4]) // => [4, 5]  (add 1 to each, then keep values > 3)

Why fpure?

Comparison

Metric fpure Ramda Sanctuary
Bundle size (gzip) ~12 KB (all 281 funcs, unminified) ~14 KB (min+gzip) ~30 KB (min+gzip)
Zero runtime dependencies Yes Yes No (type system deps)
Runtime type checks No (types at compile time only) No Yes (runtime validation)
TypeScript declarations Built-in, ships with package Requires @types/ramda Requires @types/sanctuary
Deep import for tree-shaking (CJS) Supported (require('fpure/list/map')) Supported (require('ramda/src/map')) N/A (single module)
Deep import for tree-shaking (ESM) Supported (import { map } from 'fpure/list/map') Supported (import map from 'ramda/es/map') N/A (single module)
Per-function internal dependencies 1 (curry) for 269/281; a few reuse 1–3 helpers (equals, lenses, mean) 2–8 (_curry2, _dispatchable, _xmap, etc.) Varies
Data-last sub(3)(10) 7 (10 − 3) −7 (3 − 10) 7
Data-last gt(3)(5) true (5 > 3) false (3 > 5) true
Function count 281 ~272 ~165

Ramda and Sanctuary figures are approximate, measured against their current releases (Ramda 0.32, Sanctuary 3.1). fpure figures are measured directly from this package.

Key differentiators

Data-last for ALL binary ops. Ramda's sub, div, mod, and gt/lt/gte/lte use config-first ordering that produces surprising results (sub(3)(10) = -7, gt(3)(5) = false). fpure is consistently data-last: sub(3)(10) = 7, gt(3)(5) = true.

Minimal per-function overhead. Each Ramda function imports 2–8 internal helpers (_curry2, _dispatchable, _xfilter, etc.). 269 of 281 fpure functions import only curry; a handful reuse a nearby helper instead (e.g. mean uses sum, the lens constructors use lens).

CJS & ESM tree-shakeable. fpure ships every function as a standalone file — CommonJS for Node, ESM for bundlers — each with a matching .d.ts. You can deep-import require('fpure/list/map') or import { map } from 'fpure/list/map' and get exactly that function + curry: no barrel overhead, no bundled internals, and the CJS form works in Node without a bundler. A bundled ESM deep import was measured at ~0.4 KB vs ~27 KB for the full lib. Ramda's CJS deep imports point at unbuilt source (ramda/src/map), and require('ramda') pulls in the whole library either way (a single ~351 KB bundle in versions ≤ 0.28, a full src/ barrel in newer ones).

Zero runtime type overhead. Sanctuary validates types at runtime. fpure uses TypeScript types only, meaning zero runtime cost, faster execution, smaller bundles.


Table of Contents


Curry

curry

Wraps a function so partial application returns a curried function until all args are satisfied.

const add = curry((a: number, b: number) => a + b)
add(1, 2)    // => 3
add(1)(2)    // => 3
const inc = add(1)
inc(5)       // => 6

Function Combinators

compose

Right-to-left function composition.

compose(x => x + 1, x => x * 2)(5)  // => 11  (5*2 + 1)

pipe

Left-to-right function composition.

pipe(x => x + 1, x => x * 2)(5)  // => 12  ((5+1)*2)

identity

identity(5)       // => 5
identity({a:1})   // => {a:1}

always

Creates a function that always returns the given value.

const f = always(42)
f()        // => 42
f(1, 2, 3) // => 42

T

Always returns true.

F

Always returns false.

flip

Swaps the first two arguments of a function.

const sub = curry((a, b) => a - b)
flip(sub, 3, 10)  // => -7  (calls sub(10, 3) = 3 − 10)

apply

Applies a function to an argument.

apply(x => x + 1, 5)  // => 6

tap

Runs a side-effect function on a value, returns the value.

tap(console.log, 42)  // logs 42, returns 42

juxt

Applies a list of functions to the same arguments, returns results.

juxt([x => x + 1, x => x * 2])(5)  // => [6, 10]

once

Wraps a function so it only executes once. Subsequent calls return the cached result.

memoize

Wraps a function so results are cached by argument identity (JSON-serialized).

converge

Accepts a converging function and a list of branches. Returns a function that applies branches to its args, then feeds results to the converger.

converge((a, b) => a + b, [x => x * 2, x => x * 3])(5)  // => 25  (10 + 15)

applyTo

Data-first apply: applies a value to a function.

applyTo(5, x => x + 1)  // => 6

tryCatch

Wraps a try/catch as a function.

tryCatch(x => { if (x < 0) throw Error(); return x; }, () => 0, -1)  // => 0

partial

Partially applies arguments from the left.

const add3 = (a, b, c) => a + b + c
partial(add3, 1)(2, 3)  // => 6

partialRight

Partially applies arguments from the right.

const greet = (s, t, n) => `${s}, ${t} ${n}`
partialRight(greet, 'Ms.', 'Smith')('Hello')  // => 'Hello, Ms. Smith'

curryN

Curries a function to a specific arity.

curryN(3, (a, b, c) => a + b + c)(1)(2)(3)  // => 6

ascend / descend

Creates comparator functions from a transform for sorting.

const byLength = ascend(x => x.length)
['aaa', 'b', 'cc'].sort(byLength)  // => ['b', 'cc', 'aaa']

comparator

Creates a comparator from a simple predicate.

const byFirst = comparator((a, b) => a < b)
[3, 1, 2].sort(byFirst)  // => [1, 2, 3]

composeWith / pipeWith

Compose/pipe with a custom sequencing function.

flow

Left-to-right async composition (pipe for promises).

lift

Lifts a binary function over arrays (cartesian product).

lift((a, b) => a + b, [1, 2], [3, 4])  // => [4, 5, 5, 6]

memoizeWith

Memoize with a custom key function.

otherwise

Promise error handler (.catch as a function).

o

Right-to-left composition of two unary functions.

o(x => x * 2, x => x + 1)(5)  // => 12

on

Applies a binary function to two transformed values.

on((a, b) => a + b, x => x * 2, 5, 3)  // => 16

Additional function combinators

bind, call, invoker, nAry, nthArg, of, unapply, unary, uncurryN, useWith, ascendNatural, descendNatural, promap, thunkify, liftN


Logic

both

Returns true when both predicates pass for a value.

const isPositive = x => x > 0
const isEven = x => x % 2 === 0
both(isPositive, isEven, 4)   // => true
both(isPositive, isEven, 3)   // => false

either

Returns true when either predicate passes.

either(isPositive, isEven, 4)   // => true
either(isPositive, isEven, -3)  // => false

and

Returns the first falsy argument, otherwise the last argument.

and(null, 42)  // => null
and(true, 42)  // => 42

or

Returns the first truthy argument, otherwise the last argument.

or(42, 100)   // => 42
or(null, 42)  // => 42

not

Logical negation.

not(true)   // => false
not(false)  // => true

complement

Negates a predicate function.

const isOdd = complement(x => x % 2 === 0)
isOdd(1)  // => true

ifElse

Conditional branching as a function.

ifElse(x => x > 0, x => x + 1, x => x - 1, 5)   // => 6
ifElse(x => x > 0, x => x + 1, x => x - 1, -5)  // => -6

when

Applies a function when the predicate is true, otherwise returns the value unchanged.

when(x => x > 0, x => x * 2, 5)   // => 10
when(x => x > 0, x => x * 2, -5)  // => -5

unless

Applies a function when the predicate is false, otherwise returns the value unchanged.

unless(x => x > 0, x => x * 2, -5)  // => -10
unless(x => x > 0, x => x * 2, 5)   // => 5

cond

Pattern matching via predicate/transformer pairs.

const classify = cond([
  [x => x < 0, () => 'negative'],
  [x => x === 0, () => 'zero'],
  [x => x > 0, () => 'positive'],
])
classify(-1)  // => 'negative'
classify(0)   // => 'zero'
classify(1)   // => 'positive'

allPass

Returns true when all predicates in a list pass.

allPass([x => x > 0, x => x % 2 === 0], 4)  // => true
allPass([x => x > 0, x => x % 2 === 0], 3)  // => false

anyPass

Returns true when any predicate in a list passes.

anyPass([x => x > 0, x => x % 2 === 0], 4)  // => true
anyPass([x => x > 0, x => x % 2 === 0], -3) // => false

defaultTo

Returns the default value when the input is null or undefined.

defaultTo(42, null)       // => 42
defaultTo(42, undefined)  // => 42
defaultTo(42, 0)          // => 0

isEmpty

Returns true for null, undefined, empty string, empty array, or empty object.

isEmpty(null)   // => true
isEmpty([])     // => true
isEmpty({})     // => true
isEmpty('')     // => true
isEmpty('a')    // => false

isNotEmpty

Negation of isEmpty.

isNotEmpty('a')  // => true
isNotEmpty(null) // => false

pathSatisfies

Checks if a value at a path satisfies a predicate.

pathSatisfies(x => x > 0, ['a', 'b'], {a: {b: 5}})  // => true

propSatisfies

Checks if a property satisfies a predicate.

propSatisfies(x => x > 0, 'a', {a: 5})  // => true

until

Applies a function to a value until the predicate is satisfied.

until(x => x >= 100, x => x * 2, 1)  // => 128

xor

Exclusive OR. True when exactly one argument is truthy.

xor(true, false)  // => true
xor(false, true)  // => true
xor(true, true)   // => false
xor(false, false) // => false

Math

All binary math ops use data-last: op(config, value) = value op config.

add

add(4, 5)  // => 9
add(4)(5)  // => 9

sub

sub(3, 10)  // => 7   (10 - 3)
sub(3)(10)  // => 7

mul

mul(4, 5)  // => 20

div

div(2, 10)  // => 5   (10 / 2)
div(2)(10)  // => 5

mod

mod(3, 10)  // => 1   (10 % 3)

mathMod

True modulo (not remainder). Only works with positive integer modulus.

mathMod(5, 17)   // => 2
mathMod(5, -17)  // => 3
mathMod(5, 17.2) // => NaN  (non-integer)
mathMod(-5, 17)  // => NaN  (non-positive modulus)

inc

inc(5)  // => 6

dec

dec(5)  // => 4

negate

negate(5)  // => -5

sum

sum([1, 2, 3, 4])  // => 10
sum([])            // => 0

product

product([1, 2, 3, 4])  // => 24
product([])            // => 1

mean

mean([1, 2, 3, 4])  // => 2.5

median

median([1, 2, 3, 4, 5])  // => 3
median([1, 2, 3, 4])      // => 2.5

min

min(3, 5)  // => 3

max

max(3, 5)  // => 5

clamp

clamp(1, 10, 5)   // => 5
clamp(1, 10, 0)   // => 1
clamp(1, 10, 15)  // => 10

minBy

const byLength = x => x.length
minBy(byLength, 'short', 'longer')  // => 'short'

maxBy

maxBy(byLength, 'short', 'longer')  // => 'longer'

Relation

All binary comparisons use data-last: cmp(threshold, value) = value > threshold.

gt

Greater than.

gt(3, 5)   // => true  (5 > 3)
gt(5, 3)   // => false
gt(3)(5)   // => true  "is arg > 3?"

const gt5 = gt(5)
gt5(10)    // => true
gt5(3)     // => false

lt

Less than.

lt(5, 3)   // => true  (3 < 5)
lt(3, 5)   // => false

gte

Greater than or equal.

gte(3, 5)  // => true
gte(3, 3)  // => true
gte(5, 3)  // => false

lte

Less than or equal.

lte(5, 3)  // => true
lte(3, 3)  // => true
lte(3, 5)  // => false

identical

Same-value-zero equality (like Object.is).

identical(1, 1)       // => true
identical(1, '1')     // => false
identical(NaN, NaN)   // => true
identical(0, -0)      // => false
identical([], [])     // => false

equals

Deep equality. Handles arrays, nested objects, Dates, RegExps, Maps, Sets, circular references.

equals([1, [2, 3]], [1, [2, 3]])  // => true
equals({a: 1, b: {c: 3}}, {a: 1, b: {c: 3}})  // => true
equals(new Date(0), new Date(0))  // => true

propEq

Checks if a property equals a value.

propEq(42, 'a', {a: 42})   // => true
propEq(99, 'a', {a: 42})   // => false

pathEq

Checks if a value at a path equals a target.

pathEq(42, ['a', 'b'], {a: {b: 42}})  // => true

eqBy

Compares two values after applying a transform.

eqBy(x => x.length, 'hello', 'world')  // => true

sortWith

Sorts with multiple comparators (priority order).

const data = [{name: 'a', age: 30}, {name: 'b', age: 20}]
sortWith([(a, b) => a.name.localeCompare(b.name), (a, b) => a.age - b.age], data)

differenceWith / innerJoin / symmetricDifference / symmetricDifferenceWith / unionWith

Relationship operations with custom comparators.


List

Data-last: the list is always the last argument.

map

map(x => x * 2, [1, 2, 3])  // => [2, 4, 6]

filter

filter(x => x > 1, [1, 2, 3])  // => [2, 3]

reject

reject(x => x > 1, [1, 2, 3])  // => [1]

reduce

reduce((a, b) => a + b, 0, [1, 2, 3])  // => 6

reduceRight

reduceRight((a, b) => a + b, 0, [1, 2, 3])  // => 6

find

find(x => x > 1, [1, 2, 3])  // => 2
find(x => x > 10, [1, 2, 3]) // => undefined

findIndex

findIndex(x => x > 1, [1, 2, 3])  // => 1

findLast

findLast(x => x < 3, [1, 2, 3, 2, 1])  // => 1 (last match)

findLastIndex

findLastIndex(x => x < 3, [1, 2, 3, 2, 1])  // => 4

head

head([1, 2, 3])  // => 1
head([])         // => undefined

tail

tail([1, 2, 3])  // => [2, 3]

last

last([1, 2, 3])  // => 3

init

init([1, 2, 3])  // => [1, 2]

take

take(2, [1, 2, 3, 4])  // => [1, 2]

drop

drop(2, [1, 2, 3, 4])  // => [3, 4]

takeWhile

takeWhile(x => x < 3, [1, 2, 3, 4])  // => [1, 2]

dropWhile

dropWhile(x => x < 3, [1, 2, 3, 4])  // => [3, 4]

takeLast

takeLast(2, [1, 2, 3, 4])  // => [3, 4]

dropLast

dropLast(2, [1, 2, 3, 4])  // => [1, 2]

slice

slice(1, 3, [1, 2, 3, 4])  // => [2, 3]

splitAt

splitAt(2, [1, 2, 3, 4])  // => [[1, 2], [3, 4]]

splitEvery

splitEvery(2, [1, 2, 3, 4, 5])  // => [[1, 2], [3, 4], [5]]

remove

Removes count elements starting at start.

remove(1, 2, [1, 2, 3, 4])  // => [1, 4]

insert

Inserts an element at the given index.

insert(1, 99, [1, 2, 3])  // => [1, 99, 2, 3]

insertAll

Inserts multiple elements at the given index.

insertAll(1, [99, 100], [1, 2, 3])  // => [1, 99, 100, 2, 3]

update

Replaces the element at the given index.

update(1, 99, [1, 2, 3])  // => [1, 99, 3]

adjust

Applies a function to the element at the given index.

adjust(x => x * 10, 1, [1, 2, 3])  // => [1, 20, 3]

append

append(4, [1, 2, 3])  // => [1, 2, 3, 4]

prepend

prepend(0, [1, 2, 3])  // => [0, 1, 2, 3]

concat

concat([1, 2], [3, 4])  // => [1, 2, 3, 4]

flatten

Recursively flattens nested arrays.

flatten([1, [2, [3]], 4])  // => [1, 2, 3, 4]

chain

Maps a function over a list and flattens (flatMap).

chain(x => [x, x * 2], [1, 2, 3])  // => [1, 2, 2, 4, 3, 6]

interpose

Inserts a separator between elements.

interpose(', ', [1, 2, 3])  // => [1, ', ', 2, ', ', 3]

intercalate

Inserts a separator list between sublists and flattens.

intercalate([', '], [[1], [2], [3]])  // => [1, ', ', 2, ', ', 3]

repeat

repeat(5, 3)  // => [5, 5, 5]

times

Calls a function n times with the index.

times(x => x * 2, 5)  // => [0, 2, 4, 6, 8]

zip

zip([1, 2, 3], ['a', 'b', 'c'])  // => [[1, 'a'], [2, 'b'], [3, 'c']]
zip([1, 2, 3], ['a', 'b'])       // => [[1, 'a'], [2, 'b']]

zipObj

zipObj(['a', 'b', 'c'], [1, 2, 3])  // => {a: 1, b: 2, c: 3}

zipWith

zipWith((a, b) => a + b, [1, 2, 3], [10, 20, 30])  // => [11, 22, 33]

groupBy

Groups list elements by a key function.

groupBy(x => x > 2 ? 'big' : 'small', [1, 2, 3, 4])
// => { small: [1, 2], big: [3, 4] }

groupWith

Groups consecutive equal elements.

groupWith((a, b) => a === b, [1, 1, 2, 2, 3])
// => [[1, 1], [2, 2], [3]]

sort

Creates a sorted copy (mutates nothing).

sort((a, b) => a - b, [3, 1, 2])  // => [1, 2, 3]

sortBy

Sorts by a transform function.

sortBy(x => x.length, ['aaa', 'b', 'cc'])  // => ['b', 'cc', 'aaa']

asc

Ascending numeric sort.

asc([3, 1, 2])  // => [1, 2, 3]

desc

Descending numeric sort.

desc([1, 2, 3])  // => [3, 2, 1]

all

all(x => x > 0, [1, 2, 3])   // => true
all(x => x > 1, [1, 2, 3])   // => false

any

any(x => x > 2, [1, 2, 3])   // => true
any(x => x > 10, [1, 2, 3])  // => false

none

none(x => x > 10, [1, 2, 3]) // => true
none(x => x > 1, [1, 2, 3])  // => false

includes

includes(2, [1, 2, 3])  // => true
includes(4, [1, 2, 3])  // => false

without

Removes specified values.

without([1, 2], [1, 2, 1, 3, 2, 4])  // => [3, 4]

difference

Elements in the second list not in the first.

difference([1, 2, 3], [2, 3, 4])  // => [4]

intersection

Elements in both lists.

intersection([1, 2, 3], [2, 3, 4])  // => [2, 3]

union

Unique elements from both lists.

union([1, 2, 3], [2, 3, 4])  // => [1, 2, 3, 4]

uniq

uniq([1, 2, 1, 3, 2])  // => [1, 2, 3]

uniqBy

Deduplicate by a key function.

uniqBy(x => x % 2, [1, 2, 3, 4, 5])  // => [1, 2]

uniqWith

Deduplicate with a custom comparator.

uniqWith((a, b) => a % 2 === b % 2, [1, 2, 3, 4, 5])  // => [1, 2]

aperture

Returns overlapping subsequences of length n.

aperture(2, [1, 2, 3, 4])  // => [[1, 2], [2, 3], [3, 4]]
aperture(3, [1, 2, 3, 4])  // => [[1, 2, 3], [2, 3, 4]]

partition

Splits into [pass, fail] arrays.

partition(x => x % 2 === 0, [1, 2, 3, 4, 5])
// => [[2, 4], [1, 3, 5]]

span

Splits into [takeWhile, rest].

span(x => x < 3, [1, 2, 3, 4, 1])  // => [[1, 2], [3, 4, 1]]

pluck

Extracts a key from each object in a list.

pluck('a', [{a: 1}, {a: 2}])  // => [1, 2]

project

Selects specific keys from a list of objects.

project(['a', 'b'], [{a: 1, b: 2, c: 3}, {a: 4, b: 5, c: 6}])
// => [{a: 1, b: 2}, {a: 4, b: 5}]

fromPairs

fromPairs([['a', 1], ['b', 2]])  // => {a: 1, b: 2}

toPairs

toPairs({a: 1, b: 2})  // => [['a', 1], ['b', 2]]

reverse

reverse([1, 2, 3])  // => [3, 2, 1]

length

length([1, 2, 3])  // => 3

countBy

Counts occurrences by a key function.

countBy(x => x > 2 ? 'big' : 'small', [1, 2, 3, 4])
// => { small: 2, big: 2 }

forEach

Runs a side effect for each element, returns the original array.

forEach(x => console.log(x), [1, 2, 3])
// logs 1, 2, 3 and returns [1, 2, 3]

indexBy

Indexes a list by a key function. Last duplicate wins.

indexBy(x => x.id, [{id: 'a', v: 1}, {id: 'b', v: 2}])
// => { a: {id: 'a', v: 1}, b: {id: 'b', v: 2} }

range

Generates a range of numbers.

range(1, 5)  // => [1, 2, 3, 4]

nth

Returns the nth element (supports negative indices).

nth(2, [1, 2, 3, 4])  // => 3
nth(-1, [1, 2, 3])     // => 3

transpose

Transposes a list of lists (matrix flip).

transpose([[1, 2], [3, 4]])  // => [[1, 3], [2, 4]]

xprod

Cartesian product of two arrays.

xprod([1, 2], ['a', 'b'])  // => [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]

dropRepeats

Removes consecutive duplicate elements.

dropRepeats([1, 1, 2, 2, 3])  // => [1, 2, 3]

move

Moves an element from one index to another.

move(0, 2, [1, 2, 3])  // => [2, 3, 1]

swap

Swaps two elements by index.

swap(0, 2, [1, 2, 3])  // => [3, 2, 1]

unnest

Flattens one level.

unnest([[1], [2, 3], [[4]]])  // => [1, 2, 3, [4]]

scan

Like reduce but yields all intermediate accumulators.

scan((a, b) => a + b, 0, [1, 2, 3])  // => [0, 1, 3, 6]

splitWhen / splitWhenever

Split arrays when/wherever a predicate matches.

takeLastWhile / dropLastWhile

Take/drop from the end while a predicate holds.

collectBy / reduceBy

Group/reduce by a key function.

mapAccum / mapAccumRight

Accumulate while mapping (like reduce that produces an array).

indexOf / lastIndexOf

Find the index of a value.

indexOf(2, [1, 2, 3])  // => 1

unfold

Generates a list from a seed value.

unfold(x => x < 5 ? [x, x + 1] : false, 1)  // => [1, 2, 3, 4]

transduce / traverse

Transduce with a transducer; traverse with an applicative.

reduceWhile / reduced

Reduce with early termination.

Additional list functions

dropLastWhile, dropRepeatsBy, dropRepeatsWith, pair, reduceBy, splitWhenever, swap


Object

keys

keys({a: 1, b: 2})  // => ['a', 'b']

values

values({a: 1, b: 2})  // => [1, 2]

entries

entries({a: 1, b: 2})  // => [['a', 1], ['b', 2]]

has

Own-property check.

has('a', {a: 1})   // => true
has('toString', {}) // => false

path

Safely access nested paths.

path(['a', 'b'], {a: {b: 42}})  // => 42
path(['a', 'c'], {a: {b: 42}})  // => undefined

pathOr

pathOr(99, ['a', 'c'], {a: {b: 42}})  // => 99

prop

prop('a', {a: 42})  // => 42

propOr

propOr(99, 'b', {a: 42})  // => 99

props

props(['a', 'b'], {a: 1, b: 2, c: 3})  // => [1, 2]

assoc

assoc('b', 2, {a: 1})   // => {a: 1, b: 2}
assoc('a', 2, {a: 1})   // => {a: 2}

assocPath

assocPath(['a', 'b'], 42, {})  // => {a: {b: 42}}

dissoc

dissoc('a', {a: 1, b: 2, c: 3})  // => {b: 2, c: 3}

dissocPath

dissocPath(['a', 'b'], {a: {b: 1, c: 2}, d: 3})
// => {a: {c: 2}, d: 3}

merge

merge({a: 1}, {b: 2})  // => {a: 1, b: 2}

mergeAll

mergeAll([{a: 1}, {b: 2}, {c: 3}])  // => {a: 1, b: 2, c: 3}

mergeLeft

Left-biased merge (left wins).

mergeLeft({a: 1}, {a: 2})  // => {a: 1}

mergeRight

Right-biased merge (right wins).

mergeRight({a: 1}, {a: 2})  // => {a: 2}

mergeDeepRight

Deep recursive merge.

mergeDeepRight({a: {b: 1}}, {a: {c: 2}})
// => {a: {b: 1, c: 2}}

pick

pick(['a', 'c'], {a: 1, b: 2, c: 3})  // => {a: 1, c: 3}

pickBy

pickBy(v => typeof v === 'number', {a: 1, b: 'x', c: 3})
// => {a: 1, c: 3}

omit

omit(['a', 'c'], {a: 1, b: 2, c: 3})  // => {b: 2}

omitBy

omitBy(v => typeof v === 'number', {a: 1, b: 'x', c: 3})
// => {b: 'x'}

where

Tests if an object matches a predicate spec.

where({a: x => x > 0, b: x => x.length > 1}, {a: 5, b: 'hi'})
// => true

whereEq

Tests if an object matches a value spec.

whereEq({a: 1, b: 'hi'}, {a: 1, b: 'hi', c: 3})
// => true

evolve

Recursively transforms an object's values by a spec.

evolve({a: x => x * 2, b: x => x + 1}, {a: 5, b: 10, c: 99})
// => {a: 10, b: 11, c: 99}

applySpec

Creates a function that applies args to a spec of functions.

const spec = applySpec({sum: (...xs) => xs.reduce((a,b) => a+b), max: Math.max})
spec(1, 2, 3)  // => {sum: 6, max: 3}

Lenses

Lenses provide focused access and immutability on nested data.

lensProp

const l = lensProp('a')
view(l, {a: 1, b: 2})           // => 1
set(l, 42, {a: 1, b: 2})        // => {a: 42, b: 2}
over(l, x => x + 1, {a: 1, b: 2})  // => {a: 2, b: 2}

lensPath

const l = lensPath(['a', 'b', 'c'])
view(l, {a: {b: {c: 42}}})        // => 42
set(l, 99, {a: {b: {c: 42}}})     // => {a: {b: {c: 99}}}

lensIndex

const l = lensIndex(1)
view(l, [10, 20, 30])        // => 20
set(l, 99, [10, 20, 30])     // => [10, 99, 30]
over(l, x => x + 5, [10, 20, 30])  // => [10, 25, 30]

view

Extracts the focused value.

set

Sets the focused value (returns new object, original unchanged).

over

Applies a function to the focused value.

clone

Deep clones a value (JSON roundtrip).

clone({a: 1, b: {c: 2}})  // => {a: 1, b: {c: 2}}  (new reference)

modify / modifyPath

Applies a function to a property or path.

modify('a', x => x * 2, {a: 5, b: 10})         // => {a: 10, b: 10}
modifyPath(['a', 'b'], x => x * 2, {a: {b: 5}}) // => {a: {b: 10}}

objOf

Creates a single-key object.

objOf('a', 1)  // => {a: 1}

pickAll

Like pick but includes undefined for missing keys.

renameKeys

Renames keys using a mapping.

renameKeys({a: 'x', b: 'y'}, {a: 1, b: 2})  // => {x: 1, y: 2}

whereAny

Returns true if any predicate in the spec matches.

whereAny({a: x => x > 0, b: x => x.length > 1}, {a: 0, b: 'hi'})  // => true

mergeDeepLeft

Deep merge with left values taking precedence.

mergeWith / mergeWithKey / mergeDeepWith / mergeDeepWithKey

Custom merge strategies.

Additional object functions

eqProps, forEachObjIndexed, hasIn, hasPath, invert, invertObj, keysIn, mapKeys, mapObjIndexed, paths, toPairsIn, unwind, valuesIn


String

toUpper

toUpper('hello')  // => 'HELLO'

toLower

toLower('HELLO')  // => 'hello'

trim

trim('  hello  ')  // => 'hello'

split

split(',', 'a,b,c')  // => ['a', 'b', 'c']

join

join(',', ['a', 'b', 'c'])  // => 'a,b,c'

replace

replace('world', 'there', 'hello world')  // => 'hello there'
replace(/o/g, '0', 'hello')              // => 'hell0'

test

test(/hello/, 'hello world')  // => true

match

match(/h(\w)/, 'hello')  // => ['he', 'e']  (RegExpMatchArray)

startsWith

startsWith('hello', 'hello world')  // => true

endsWith

endsWith('world', 'hello world')  // => true

padStart

padStart(5, '0', '42')  // => '00042'

padEnd

padEnd(5, '0', '42')  // => '42000'

toString

toString(42)     // => '42'
toString(null)   // => 'null'
toString([1,2])  // => '1,2'

Type

type

Returns the internal [[Class]] name.

type({})             // => 'Object'
type([])             // => 'Array'
type(null)           // => 'Null'
type(undefined)      // => 'Undefined'
type(() => {})       // => 'Function'
type(/[A-Z]/)        // => 'RegExp'

is

Checks if a value is an instance of a constructor.

is(Array, [])     // => true
is(RegExp, /x/)   // => true

isNil

isNil(null)       // => true
isNil(undefined)  // => true
isNil(0)          // => false

isNotNil

isNotNil(0)       // => true
isNotNil(null)    // => false

isArray

isArray([])   // => true
isArray({})   // => false

isObject

Plain object check (not array, not null).

isObject({})      // => true
isObject([])      // => false
isObject(null)    // => false

isString

isString('hello')  // => true
isString(42)       // => false

isNumber

Excludes NaN.

isNumber(42)   // => true
isNumber(NaN)  // => false

isBoolean

isBoolean(true)   // => true
isBoolean(false)  // => true
isBoolean(0)      // => false

isFunction

isFunction(() => {})  // => true

isDate

isDate(new Date())  // => true

isRegExp

isRegExp(/test/)  // => true

isError

isError(new Error())  // => true

isPromise

Detects thenables.

isPromise(Promise.resolve())  // => true
isPromise({then: () => {}})  // => true

isInteger

isInteger(42)    // => true
isInteger(3.14)  // => false

isFiniteNum

isFiniteNum(42)       // => true
isFiniteNum(Infinity) // => false
isFiniteNum(NaN)      // => false

isNaNVal

isNaNVal(NaN)  // => true
isNaNVal(42)   // => false

Design

Parameter Ordering

All binary operations follow data-last, config-first: the thing you're likely to partially apply comes first, the data comes last.

Function Call Meaning Ramda (for comparison)
sub(3)(10) 10 - 3 = 7 subtract 3 from 10 R.subtract(3,10) = -7
div(2)(10) 10 / 2 = 5 divide 10 by 2 R.divide(2,10) = 0.2
gt(3)(5) 5 > 3 = true is arg > 3? R.gt(3,5) = false
mod(3)(10) 10 % 3 = 1 10 mod 3 R.mod(3,10) = 3

Immutability

Every function returns a new value. Inputs are never mutated.

Purity

No side effects in logic functions. tap and forEach are the only exceptions since they're explicitly for side effects.

No Inference

Unlike Ramda, fpure never inspects argument types to guess your intent. TypeScript handles type safety at compile time. Runtime is strict.


Development

Clone the repo and make changes to lib/. TypeScript is compiled to dist/ before running tests.

npm install        # install dev dependencies (TypeScript, c8)
npm test           # compiles lib/ → dist/, then runs tests

About

Pure functional programming library

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages