# `fpure`

[![Coverage Status](https://coveralls.io/repos/github/seancannon/fpure/badge.svg)](https://coveralls.io/github/seancannon/fpure)

Pure, curried, functional programming utilities for TypeScript.

Inspired by Ramda and Sanctuary

## Install

```sh
npm install fpure
```

## Usage Example

```ts
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)
- [Function Combinators](#function-combinators)
- [Logic](#logic)
- [Math](#math)
- [Relation](#relation)
- [List](#list)
- [Object](#object)
- [String](#string)
- [Type](#type)

---

## Curry

### `curry`

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

```ts
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.

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

### `pipe`

Left-to-right function composition.

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

### `identity`

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

### `always`

Creates a function that always returns the given value.

```ts
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.

```ts
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.

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

### `tap`

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

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

### `juxt`

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

```ts
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.

```ts
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.

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

### `tryCatch`

Wraps a try/catch as a function.

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

### `partial`

Partially applies arguments from the left.

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

### `partialRight`

Partially applies arguments from the right.

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

### `curryN`

Curries a function to a specific arity.

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

### `ascend` / `descend`

Creates comparator functions from a transform for sorting.

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

### `comparator`

Creates a comparator from a simple predicate.

```ts
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).

```ts
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.

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

### `on`

Applies a binary function to two transformed values.

```ts
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.

```ts
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.

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

### `and`

Returns the first falsy argument, otherwise the last argument.

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

### `or`

Returns the first truthy argument, otherwise the last argument.

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

### `not`

Logical negation.

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

### `complement`

Negates a predicate function.

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

### `ifElse`

Conditional branching as a function.

```ts
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.

```ts
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.

```ts
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.

```ts
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.

```ts
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.

```ts
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.

```ts
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.

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

### `isNotEmpty`

Negation of `isEmpty`.

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

### `pathSatisfies`

Checks if a value at a path satisfies a predicate.

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

### `propSatisfies`

Checks if a property satisfies a predicate.

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

### `until`

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

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

### `xor`

Exclusive OR. True when exactly one argument is truthy.

```ts
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`

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

### `sub`

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

### `mul`

```ts
mul(4, 5)  // => 20
```

### `div`

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

### `mod`

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

### `mathMod`

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

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

### `inc`

```ts
inc(5)  // => 6
```

### `dec`

```ts
dec(5)  // => 4
```

### `negate`

```ts
negate(5)  // => -5
```

### `sum`

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

### `product`

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

### `mean`

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

### `median`

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

### `min`

```ts
min(3, 5)  // => 3
```

### `max`

```ts
max(3, 5)  // => 5
```

### `clamp`

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

### `minBy`

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

### `maxBy`

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

---

## Relation

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

### `gt`

Greater than.

```ts
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.

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

### `gte`

Greater than or equal.

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

### `lte`

Less than or equal.

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

### `identical`

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

```ts
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.

```ts
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.

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

### `pathEq`

Checks if a value at a path equals a target.

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

### `eqBy`

Compares two values after applying a transform.

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

### `sortWith`

Sorts with multiple comparators (priority order).

```ts
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`

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

### `filter`

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

### `reject`

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

### `reduce`

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

### `reduceRight`

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

### `find`

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

### `findIndex`

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

### `findLast`

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

### `findLastIndex`

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

### `head`

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

### `tail`

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

### `last`

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

### `init`

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

### `take`

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

### `drop`

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

### `takeWhile`

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

### `dropWhile`

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

### `takeLast`

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

### `dropLast`

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

### `slice`

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

### `splitAt`

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

### `splitEvery`

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

### `remove`

Removes `count` elements starting at `start`.

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

### `insert`

Inserts an element at the given index.

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

### `insertAll`

Inserts multiple elements at the given index.

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

### `update`

Replaces the element at the given index.

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

### `adjust`

Applies a function to the element at the given index.

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

### `append`

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

### `prepend`

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

### `concat`

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

### `flatten`

Recursively flattens nested arrays.

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

### `chain`

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

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

### `interpose`

Inserts a separator between elements.

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

### `intercalate`

Inserts a separator list between sublists and flattens.

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

### `repeat`

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

### `times`

Calls a function `n` times with the index.

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

### `zip`

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

### `zipObj`

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

### `zipWith`

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

### `groupBy`

Groups list elements by a key function.

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

### `groupWith`

Groups consecutive equal elements.

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

### `sort`

Creates a sorted copy (mutates nothing).

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

### `sortBy`

Sorts by a transform function.

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

### `asc`

Ascending numeric sort.

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

### `desc`

Descending numeric sort.

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

### `all`

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

### `any`

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

### `none`

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

### `includes`

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

### `without`

Removes specified values.

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

### `difference`

Elements in the second list not in the first.

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

### `intersection`

Elements in both lists.

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

### `union`

Unique elements from both lists.

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

### `uniq`

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

### `uniqBy`

Deduplicate by a key function.

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

### `uniqWith`

Deduplicate with a custom comparator.

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

### `aperture`

Returns overlapping subsequences of length `n`.

```ts
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.

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

### `span`

Splits into `[takeWhile, rest]`.

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

### `pluck`

Extracts a key from each object in a list.

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

### `project`

Selects specific keys from a list of objects.

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

### `fromPairs`

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

### `toPairs`

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

### `reverse`

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

### `length`

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

### `countBy`

Counts occurrences by a key function.

```ts
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.

```ts
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.

```ts
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.

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

### `nth`

Returns the nth element (supports negative indices).

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

### `transpose`

Transposes a list of lists (matrix flip).

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

### `xprod`

Cartesian product of two arrays.

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

### `dropRepeats`

Removes consecutive duplicate elements.

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

### `move`

Moves an element from one index to another.

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

### `swap`

Swaps two elements by index.

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

### `unnest`

Flattens one level.

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

### `scan`

Like reduce but yields all intermediate accumulators.

```ts
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.

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

### `unfold`

Generates a list from a seed value.

```ts
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`

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

### `values`

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

### `entries`

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

### `has`

Own-property check.

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

### `path`

Safely access nested paths.

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

### `pathOr`

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

### `prop`

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

### `propOr`

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

### `props`

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

### `assoc`

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

### `assocPath`

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

### `dissoc`

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

### `dissocPath`

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

### `merge`

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

### `mergeAll`

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

### `mergeLeft`

Left-biased merge (left wins).

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

### `mergeRight`

Right-biased merge (right wins).

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

### `mergeDeepRight`

Deep recursive merge.

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

### `pick`

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

### `pickBy`

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

### `omit`

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

### `omitBy`

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

### `where`

Tests if an object matches a predicate spec.

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

### `whereEq`

Tests if an object matches a value spec.

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

### `evolve`

Recursively transforms an object's values by a spec.

```ts
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.

```ts
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`

```ts
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`

```ts
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`

```ts
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).

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

### `modify` / `modifyPath`

Applies a function to a property or path.

```ts
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.

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

### `pickAll`

Like `pick` but includes `undefined` for missing keys.

### `renameKeys`

Renames keys using a mapping.

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

### `whereAny`

Returns true if any predicate in the spec matches.

```ts
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`

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

### `toLower`

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

### `trim`

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

### `split`

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

### `join`

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

### `replace`

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

### `test`

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

### `match`

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

### `startsWith`

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

### `endsWith`

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

### `padStart`

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

### `padEnd`

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

### `toString`

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

---

## Type

### `type`

Returns the internal `[[Class]]` name.

```ts
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.

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

### `isNil`

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

### `isNotNil`

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

### `isArray`

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

### `isObject`

Plain object check (not array, not null).

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

### `isString`

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

### `isNumber`

Excludes NaN.

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

### `isBoolean`

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

### `isFunction`

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

### `isDate`

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

### `isRegExp`

```ts
isRegExp(/test/)  // => true
```

### `isError`

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

### `isPromise`

Detects thenables.

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

### `isInteger`

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

### `isFiniteNum`

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

### `isNaNVal`

```ts
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.

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