canary-js
Version:
A minimalist, functional JavaScript toolkit for mere mortals. Curried, data-last, and easy to learn.
860 lines (539 loc) • 17.1 kB
Markdown
# CanaryJS Function Reference
A minimalist toolkit deserves minimalist docs. Below you'll find all of CanaryJS's functions—clear, consistent, and cheerfully curried.
---
## Arrays
### `map`
**Description:** Applies a function to each item in a list.
**Signature:** `(a → b) → [a] → [b]`
```js
map(x => x * 2)([1, 2, 3]) // → [2, 4, 6]
```
### `filter`
**Description:** Filters a list based on a predicate function.
**Signature:** `(a → Boolean) → [a] → [a]`
```js
filter(x => x > 1)([1, 2, 3]) // → [2, 3]
```
### `reduce`
**Description:** Reduces a list to a single value.
**Signature:** `((b, a) → b) → b → [a] → b`
```js
reduce((acc, x) => acc + x)(0)([1, 2, 3]) // → 6
```
### `slice`
**Description:** Returns a section of a list.
**Signature:** `Number → Number → [a] → [a]`
```js
slice(1)(3)([0, 1, 2, 3]) // → [1, 2]
```
### `find`
**Description:** Finds the first element matching a predicate.
**Signature:** `(a → Boolean) → [a] → a | undefined`
```js
find(x => x > 1)([1, 2, 3]) // → 2
```
### `findIndex`
**Description:** Returns the index of the first element matching a predicate.
**Signature:** `(a → Boolean) → [a] → Number`
```js
findIndex(x => x === 2)([1, 2, 3]) // → 1
```
### `findLast`
**Description:** Finds the last element matching a predicate.
**Signature:** `(a → Boolean) → [a] → a | undefined`
```js
findLast(x => x % 2 === 0)([1, 2, 4, 6, 3]) // → 6
```
### `some` (alias: `any`)
**Description:** Returns true if at least one element matches a predicate.
**Signature:** `(a → Boolean) → [a] → Boolean`
```js
some(x => x > 2)([1, 2, 3]) // → true
```
### `every` (alias: `all`)
**Description:** Returns true if all elements match a predicate.
**Signature:** `(a → Boolean) → [a] → Boolean`
```js
every(x => x < 5)([1, 2, 3]) // → true
```
### `at` (alias: `nth`)
**Description:** Gets the element at a given index (supports negative indexing).
**Signature:** `Number → [a] → a | undefined`
```js
at(-1)([1, 2, 3]) // → 3
```
### `includes`
**Description:** Checks if a value is present in an array or string.
**Signature:** `a → [a] | String → Boolean`
```js
includes(2)([1, 2, 3]) // → true
includes('a')('cat') // → true
```
### `sort`
**Description:** Sorts a list using default or custom comparison.
**Signature:** `((a, a) → Number) → [a] → [a]`
```js
sort((a, b) => a - b)([3, 1, 2]) // → [1, 2, 3]
```
### `reverse`
**Description:** Reverses the list.
**Signature:** `[a] → [a]`
```js
reverse([1, 2, 3]) // → [3, 2, 1]
```
### `flatMap`
**Description:** Maps and flattens the result.
**Signature:** `(a → [b]) → [a] → [b]`
```js
flatMap(x => [x, x])([1, 2]) // → [1, 1, 2, 2]
```
### `flat`
**Description:** Flattens one level of nested arrays.
**Signature:** `[[a]] → [a]`
```js
flat([[1], [2, 3]]) // → [1, 2, 3]
```
### `concat`
**Description:** Concatenates two arrays.
**Signature:** `[a] → [a] → [a]`
```js
concat([1, 2])([3]) // → [1, 2, 3]
```
### `join`
**Description:** Joins array elements into a string.
**Signature:** `String → [a] → String`
```js
join('-')([1, 2, 3]) // → '1-2-3'
```
### `indexOf`
**Description:** Finds the first index of a value.
**Signature:** `a → [a] → Number`
```js
indexOf(2)([1, 2, 3]) // → 1
```
### `lastIndexOf`
**Description:** Finds the last index of a value.
**Signature:** `a → [a] → Number`
```js
lastIndexOf(2)([1, 2, 3, 2]) // → 3
```
### `length`
**Description:** Returns the length of an array or string.
**Signature:** `[a] | String → Number`
```js
length([1, 2, 3]) // → 3
length('abc') // → 3
```
---
## Strings
### `split`
**Description:** Splits a string by a separator.
**Signature:** `String → String → [String]`
```js
split('-')('a-b-c') // → ['a', 'b', 'c']
```
### `trim`
**Description:** Trims whitespace from both ends of a string.
**Signature:** `String → String`
```js
trim(' hello ') // → 'hello'
```
### `trimStart`
**Description:** Removes leading whitespace from a string.
**Signature:** `String → String`
```js
trimStart(' hello') // → 'hello'
```
### `trimEnd`
**Description:** Removes trailing whitespace from a string.
**Signature:** `String → String`
```js
trimEnd('hello ') // → 'hello'
```
### `repeat`
**Description:** Repeats the string `n` times.
**Signature:** `Number → String → String`
```js
repeat(3)('hi') // → 'hihihi'
```
### `padStart`
**Description:** Pads the beginning of a string with spaces until it reaches the given length.
**Signature:** `Number → String → String`
```js
padStart(5)('42') // → ' 42'
```
### `padEnd`
**Description:** Pads the end of a string with spaces until it reaches the given length.
**Signature:** `Number → String → String`
```js
padEnd(5)('42') // → '42 '
```
### `toUpper`
**Description:** Converts a string to uppercase.
**Signature:** `String → String`
```js
toUpper('hi') // → 'HI'
```
### `startsWith`
**Description:** Checks if a string starts with the value.
**Signature:** `String → Boolean`
```js
startsWith('he')('hello') // → true
```
### `endsWith`
**Description:** Checks if a string ends with the value.
**Signature:** `String → Boolean`
```js
startsWith('a')('hello') // → false
```
### `toLower`
**Description:** Converts a string to lowercase.
**Signature:** `String → String`
```js
toLower('HI') // → 'hi'
```
---
## Numbers
### `add`
**Description:** Adds two numbers.
**Signature:** `Number → Number → Number`
```js
add(2)(3) // → 5
```
### `subtract`
**Description:** Subtracts the second number from the first.
**Signature:** `Number → Number → Number`
```js
subtract(5)(2) // → 3
```
### `multiply`
**Description:** Multiplies two numbers.
**Signature:** `Number → Number → Number`
```js
multiply(3)(4) // → 12
```
### `divide`
**Description:** Divides the first number by the second.
**Signature:** `Number → Number → Number`
```js
divide(10)(2) // → 5
```
### `modulo`
**Description:** Gets the remainder of division.
**Signature:** `Number → Number → Number`
```js
modulo(10)(3) // → 1
```
### `abs`
**Description:** Returns the absolute value of a number.
**Signature:** `Number → Number`
```js
abs(-7) // → 7
```
### `floor`
**Description:** Rounds a number down to the nearest integer.
**Signature:** `Number → Number`
```js
floor(4.8) // → 4
```
### `round`
**Description:** Rounds a number to the nearest integer.
**Signature:** `Number → Number`
```js
round(4.3) // → 4
```
### `ceil`
**Description:** Rounds a number up to the nearest integer.
**Signature:** `Number → Number`
```js
ceil(4.1) // → 5
```
### `pow`
**Description:** Raises a base number to an exponent.
**Signature:** `Number → Number → Number`
```js
pow(2)(3) // → 8
```
---
## Objects
### `keys`
**Description:** Gets the keys of an object.
**Signature:** `Object → [String]`
```js
keys({ a: 1, b: 2 }) // → ['a', 'b']
```
### `values`
**Description:** Gets the values of an object.
**Signature:** `Object → [a]`
```js
values({ a: 1, b: 2 }) // → [1, 2]
```
### `entries`
**Description:** Gets an array of key-value pairs.
**Signature:** `Object → [[String, a]]`
```js
entries({ a: 1 }) // → [['a', 1]]
```
### `fromEntries`
**Description:** Builds an object from key-value pairs.
**Signature:** `[[String, a]] → Object`
```js
fromEntries([['a', 1]]) // → { a: 1 }
```
### `prop`
**Description:** Gets a property from an object.
**Signature:** `String → Object → a`
```js
prop('a')({ a: 1, b: 2 }) // → 1
```
### `freeze`
**Description:** Makes an object immutable by freezing its properties.
**Signature:** `Object → Object`
```js
const obj = freeze({ a: 1 })
obj.a = 2
obj.a // → 1
```
### `seal`
**Description:** Seals an object to prevent new properties from being added or existing properties from being removed.
**Signature:** `Object → Object`
```js
const obj = seal({ a: 1 })
obj.b = 2
obj.b // → undefined
```
### `is`
**Description:** Determines whether two values are the same value (like `===` but handles `NaN` and `-0` correctly).
**Signature:** `a → a → Boolean`
```js
is(NaN)(NaN) // → true
is(0)(-0) // → false
```
## Comparison & Boolean
### `not`
**Description:** Logical NOT.
**Signature:** `a → Boolean`
```js
not(true) // → false
```
### `equals`
**Description:** Checks strict equality.
**Signature:** `a → a → Boolean`
```js
equals(2)(2) // → true
```
### `lt`
**Description:** Checks if the first value is less than the second.
**Signature:** `Number → Number → Boolean`
```js
lt(10)(5) // → true
```
### `lte`
**Description:** Checks if the first value is less than or equal to the second.
**Signature:** `Number → Number → Boolean`
```js
lte(10)(10) // → true
```
### `gt`
**Description:** Checks if the first value is greater than the second.
**Signature:** `Number → Number → Boolean`
```js
gt(5)(10) // → true
```
### `gte`
**Description:** Checks if the first value is greater than or equal to the second.
**Signature:** `Number → Number → Boolean`
```js
gte(5)(5) // → true
```
---
## Core Utilities
### `identity`
**Description:** Returns the same value it was given.
**Signature:** `a → a`
```js
identity(42) // → 42
```
### `always`
**Description:** Returns a constant function.
**Signature:** `a → () → a`
```js
always(3)() // → 3
```
### `converge`
**Description:** Applies two functions to the same input, then combines their results using a curried function.
**Signature:** `(b → c → d) → (a → b) → (a → c) → a → d`
**Note:**
This is a curried version of what’s known in functional programming as the **S combinator**. It allows you to fork a single input into two branches, apply different functions to each, and then recombine them with a third.
```js
const double = x => x * 2
const increment = x => x + 1
const add = a => b => a + b
const compute = converge(add)(double)(increment)
compute(3) // → 10 (double(3) + increment(3) → 6 + 4)
```
### `tap`
**Description:** Applies a function for side effects, then returns the input.
**Signature:** `(a → any) → a → a`
```js
tap(console.log)(42) // → logs 42, returns 42
```
### `pipe`
**Description:** Composes functions left-to-right.
**Signature:** `[(a → b), ..., (y → z)] → a → z`
```js
pipe(
x => x + 1,
x => x * 2
)(3) // → 8
```
### `compose`
**Description:** Composes functions right-to-left.
**Signature:** `[(y → z), ..., (a → b)] → a → z`
```js
compose(
x => x * 2,
x => x + 1
)(3) // → 8
```
### `curry`
**Description:** Converts a multi-arg function into a chain of unary functions.
**Signature:** `(a, b, ...) → c → a → b → ... → c`
```js
curry((a, b) => a + b)(1)(2) // → 3
```
### `uncurry`
**Description:** Flattens a curried function into a single call.
**Signature:** `(a → b → c) → (a, b) → c`
```js
uncurry(a => b => a + b)(1, 2) // → 3
```
### `addIndex`
**Description:** Allows `map`, `filter` etc. to receive the index.
**Signature:** `((a, Number) → b) → [a] → [b]`
```js
addIndex(map)((x, i) => x + i)([10, 20, 30]) // → [10, 21, 32]
```
### `flip`
**Description:** Reverses the order of arguments for a curried binary function.
**Signature:** `(a → b → c) → b → a → c`
```js
const subtract = a => b => a - b
const flipped = flip(subtract)
flipped(2)(10) // → -8
```
### `binary`
**Description:** Converts a curried binary function into a standard 2-argument function, useful for `reduce`, `sort`, and other native JS methods.
**Signature:** `(a → b → c) → (a, b) → c`
```js
const sum = binary(add)[(1, 2, 3)].reduce(sum) // → 6
const cmp = binary(subtract)[(3, 2, 1)].sort(cmp) // → [1, 2, 3]
```
### `trinary`
**Description:** Converts a curried ternary function into a standard 3-argument function. Useful when working with methods like `map`, `filter`, or `forEach` that provide `(element, index, array)`.
**Signature:** `(a → b → c → d) → (a, b, c) → d`
```js
const describe = trinary(x => i => arr => `${x} at ${i} in ${arr}`)[
(10, 20, 30)
].map(describe)
// → ['10 at 0 in [10, 20, 30]', '20 at 1 in [10, 20, 30]', '30 at 2 in [10, 20, 30]']
```
---
## Control Flow
### `ifElse`
**Description:** Branches logic based on a predicate.
**Signature:** `(a → Boolean) → (a → b) → (a → b) → a → b`
```js
ifElse(x => x > 0)(() => 'positive')(() => 'non-positive')(1) // → 'positive'
```
### `cond`
**Description:** Matches the first predicate and applies its function.
**Signature:** `[[a → Boolean, a → b]] → a → b`
```js
cond([
[x => x < 0, () => 'negative'],
[x => x > 0, () => 'positive'],
])(-1) // → 'negative'
```
### `tryCatch`
**Description:** Wraps a potentially unsafe function and provides a fallback in case of error. The fallback receives both the error and the original input. Useful in pipelines when working with functions that might throw (like `JSON.parse`, `decodeURIComponent`, etc).
**Signature:** `(a → b) → (Error, a → b) → a → b`
```js
const safeParse = tryCatch(JSON.parse, (err, input) => {
console.warn('Parse failed for:', input, err.message)
return {}
})
safeParse('{"ok": true}') // → { ok: true }
safeParse('not JSON') // → {} and logs a warning
```
---
## Discriminated Union Helpers
### `match`
**Description:** A plain function for pattern matching on discriminated unions (e.g., those created with Zod’s `z.discriminatedUnion`). You provide the value and an object mapping `kind (or tag)` values to handler functions. Optionally, include a fallback handler via _. The function dispatches to the appropriate handler based on the union’s kind property.
**Signature:** `(state: { kind: string, ... }, cases: { [kind]: (val) → b, ... , _?: (val) → b }) → b`
```js
import { z } from 'zod'
// Define a discriminated union type
const Shape = z.discriminatedUnion('kind', [
z.object({ kind: z.literal('circle'), radius: z.number().positive() }),
z.object({
kind: z.literal('rectangle'),
width: z.number().positive(),
height: z.number().positive(),
}),
])
// Using match without a fallback (must cover all cases explicitly)
const area = (shape: z.infer<typeof Shape>) =>
match(shape, {
circle: ({ radius }) => Math.PI * radius * radius, // handler for 'circle'
rectangle: ({ width, height }) => width * height, // handler for 'rectangle'
})
console.log(area({ kind: 'circle', radius: 2 })) // → 12.566...
console.log(area({ kind: 'rectangle', width: 3, height: 4 })) // → 12
// With fallback handler for non-exhaustive unions
const describe = (shape: any) =>
match(shape, {
circle: ({ radius }) => `Circle of radius ${radius}`, // specific handler
_: s => `Unknown shape: ${s.kind}`, // fallback handler
})
console.log(describe({ kind: 'circle', radius: 5 })) // → "Circle of radius 5"
console.log(describe({ kind: 'triangle', base: 3, height: 4 })) // → "Unknown shape: triangle"
```
### `matchStrict`
**Description:**
A strict, exhaustive version of `match` for pattern matching on discriminated unions. Unlike `match`, `matchStrict` **requires that every possible `kind` in the union be handled** and **does not allow a fallback (`_`) case**. This provides **compile-time exhaustiveness checking**, similar to pattern matching in Elm, OCaml, or Haskell. If a new variant is added to the union and not handled, TypeScript will raise an error.
**Signature:**
`(state: { kind: PropertyKey, ... }, cases: { [kind]: (val) → b }) → b`
*(All `kind` values must be present. No `_` fallback is allowed.)*
```js
import { z } from 'zod'
import { matchStrict } from 'canary-js'
// Define a discriminated union type
const Shape = z.discriminatedUnion('kind', [
z.object({ kind: z.literal('circle'), radius: z.number().positive() }),
z.object({
kind: z.literal('rectangle'),
width: z.number().positive(),
height: z.number().positive(),
}),
])
// Using matchStrict (must be fully exhaustive)
const area = (shape: z.infer<typeof Shape>) =>
matchStrict(shape, {
circle: ({ radius }) => Math.PI * radius * radius,
rectangle: ({ width, height }) => width * height,
})
console.log(area({ kind: 'circle', radius: 2 })) // → 12.566...
console.log(area({ kind: 'rectangle', width: 3, height: 4 })) // → 12
// ❌ This WILL fail at compile time:
// const badArea = (shape: z.infer<typeof Shape>) =>
// matchStrict(shape, {
// circle: ({ radius }) => Math.PI * radius * radius,
// // rectangle case is missing → TypeScript error
// })
// ❌ This is also NOT allowed (no fallback in matchStrict):
// matchStrict(shape, {
// circle: ({ radius }) => radius,
// _: () => 0,
// })
```