Skip to content

Instantly share code, notes, and snippets.

@stefanmaric
Last active December 18, 2022 18:30
Show Gist options
  • Save stefanmaric/ef176e40e35040d83a21d413a27dfdd1 to your computer and use it in GitHub Desktop.
Save stefanmaric/ef176e40e35040d83a21d413a27dfdd1 to your computer and use it in GitHub Desktop.
one-liner functional utilities in JavaScript
/**
* @file one-liner functional utilities in JavaScript
* @author Stefan Maric <[email protected]>
* @license
* Copyright © 2017 Stefan Maric <[email protected]>
* This work is free. You can redistribute it and/or modify it under the
* terms of the Do What The Fuck You Want To Public License, Version 2,
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
*/
// PARTIAL APPLICATION
/// /////////////////////////////////////////////////////////////////////////////
/**
* Partially applies `f` to arguments `args`, that's it, returns a function that
* invokes `f` with `args` prepended to the arguments it receives.
* @param {Function} f Function to be partially applied.
* @param {*} args Arguments to partially apply.
* @return {Function} Partially applied function.
* @lodash partial
* @ramda partial
* @example
*
* const sum = (a, b) => a + b
* const sumTen = partial(sum, 10)
*
* sumTen(5)
* // => 15
*/
const partial = (f, ...args) => (...rest) => f(...args, ...rest)
/**
* Similar to partial, but arguments applied from right to left: Partially
* applies `f` to arguments `args`, that's it, returns a function that invokes
* `f` with `args` appended to the arguments it receives.
* @param {Function} f Function to be partially applied.
* @param {*} args Arguments to partially apply.
* @return {Function} Partially applied function.
* @lodash partialRight
* @ramda partialRight
* @example
*
* const pretty = partialR(JSON.stringify, null, 2)
*
* pretty({ name: 'John', age: 28, children: [{ name: 'Carlos', age: 8 }] })
* // => "{
* // "name": "John",
* // "age": 28,
* // "children": [
* // {
* // "name": "Carlos",
* // "age": 8
* // }
* // ]
* // }"
*/
const partialR = (f, ...args) => (...rest) => f(...rest, ...args)
// CURRYING
/// /////////////////////////////////////////////////////////////////////////////
/**
* Automagical currying. Creates a function that invokes `f` if all expected
* arguments are passed, or returns a function that expects the rest of the
* arguments.
*
* Strict curry alternative:
* `f => x => f.length > 1 ? curry(f.bind(null, x)) : f(x)`
* @param {Function} f Function to be curried.
* @return {Function} Curried function.
* @lodash curry
* @ramda curry
* @example
*
* const foo = (a, b, c) => [a, b, c]
*
* foo(1, 2, 3)
* // => [1, 2, 3]
*
* const curriedFoo = curry(foo)
*
* curriedFoo(1)
* // => Function
*
* curriedFoo(1)(2)(3)
* // => [1, 2, 3]
*
* curriedFoo(1)(2, 3)
* // => [1, 2, 3]
*
* curriedFoo(1, 2)(3)
* // => [1, 2, 3]
*
* curriedFoo(1)()(2)()(3)
* // => [1, 2, 3]
*
* curriedFoo(1)(undefined)(3)
* // => [1, undefined, 3]
*/
const curry = f => (...args) =>
args.length < f.length ? curry(f.bind(null, ...args)) : f(...args)
/**
* Just like curry, but arguments are partially applied from right to left.
*
* Strict curryR alternative:
* `(f, ari = f.length) => x => ari <= 1 ? f(x) : curryR((...args) => f(...args, x), ari - 1)`
*
* Alternative using partialR:
* `(f, ari = f.length) => (...args) => args.length >= ari ? f(...args) : curryR(partialR(f, ...args), ari - args.length)`
*
* @param {Function} f Function to be curried.
* @param {number} [ari=f.length] The expected arity of `f`.
* @lodash curryRight
* @ramda ?
* @return {Function} Curried function.
* @example
*
* const bar = (a, b, c) => [a, b, c]
*
* bar(1, 2, 3)
* // => [1, 2, 3]
*
* const curriedBar = curryR(bar)
*
* curriedBar(1)
* // => Function
*
* curriedBar(1)(2)(3)
* // => [3, 2, 1]
*
* curriedBar(1)(2, 3)
* // => [2, 3, 1]
*
* curriedBar(1, 2)(3)
* // => [3, 1, 2]
*
* curriedBar(1)()(2)()(3)
* // => [3, 2, 1]
*
* curriedBar(1)(undefined)(3)
* // => [3, undefined, 1]
*/
const curryR = (f, ari = f.length) => (...args) =>
args.length < ari
? curryR((...rest) => f(...rest, ...args), ari - args.length)
: f(...args)
// FUNCTION COMPOSITION
/**
* Compose functions from right to left — creates a function than, when invoked,
* calls the last function in the array with its arguments, the result of it is
* passed as argument to the previous function and so on until the value is
* returned.
*
* Simpler alternative: `fns => x => fns.reduceRight((v, f) => f(v), x)`
* @param {Array<Function>} fns Functions to be composed.
* @return {Function} Composition of the functions.
* @lodash ?
* @ramda compose
* @example
*
* const reverse = s => s.split('').reverse().join('')
* const lowercase = s => s.toLowerCase()
* const capitalize = s => s.replace(/\b\w/g, c => c.toUpperCase())
* const bang = s => s + '!'
*
* const anadrome = compose([
* bang,
* capitalize,
* lowercase,
* reverse
* ])
*
* anadrome('Redraw Dog Flow')
* // => "Wolf God Warder!"
*/
const compose = ([head, ...rest]) => x => (head ? head(compose(rest)(x)) : x)
/**
* Compose functions from left to right — creates a function that, when invoked,
* calls the first function in the array with its arguments, the result of it is
* passed as argument to the next function and so on until the value is
* returned.
*
* Simpler alternative: `fns => x => fns.reduce((v, f) => f(v), x)`
* @param {Array<Function>} fns Functions to be composed.
* @return {Function} Composition of the functions.
* @lodash flow
* @ramda pipe
* @example
*
* const reverse = s => s.split('').reverse().join('')
* const lowercase = s => s.toLowerCase()
* const capitalize = s => s.replace(/\b\w/g, c => c.toUpperCase())
* const bang = s => s + '!'
*
* const anadrome = flow([
* reverse,
* lowercase,
* capitalize,
* bang
* ])
*
* anadrome('Redraw Dog Flow')
* // => "Wolf God Warder!"
*/
const flow = ([head, ...rest]) => x => (head ? flow(rest)(head(x)) : x)
/**
* Compose async and sync operations seamless.
* @param {Array<Function>} fns Functions to compose.
* @return {Function} Composition of the functions.
* @lodash ?
* @lamda pipeP
* @example
*
* const wait = (ms, x) => new Promise(resolve => setTimeout(() => resolve(x), ms))
*
* const doStuff = asyncFlow([
* async n => (n + await wait(1000, 4)) * await wait(2000, 6),
* n => n % 10,
* async n => n / await wait(1000, 2)
* ])
*
* doStuff(Promise.resolve(7))
* .then(console.log)
* .catch(console.error)
* // => 3
*/
const asyncFlow = fns => x =>
fns.reduce((v, f) => v.then(f), Promise.resolve(x))
// FUNCTION UTILITIES
/// /////////////////////////////////////////////////////////////////////////////
/**
* Creates a function that takes an array of arguments `args` and inkokes `f`
* passing each item of `args` as function arguments, similar to Function#apply.
*
* This allows to convert a variadic or n-ary function into an unary function.
* @param {Function} f Variadic/n-ary function to apply.
* @return {Function} Converted unary function.
* @lodash spread
* @ramda apply
* @example
*
* Math.max(1, 2, 3)
* // => 3
*
* apply(Math.max)([1,2,3])
* // => 3
*/
const apply = f => args => f(...args)
/**
* The opposite of `apply` — creates a function that takes any number of
* arguments and invokes `f` passing them as an array.
* @param {Function} f Unary function to unapply.
* @return {Function} Converted variadic function.
* @lodash ?
* @ramda unapply
* @example
*
* Array.from([1, 2, 3])
* // => [1, 2, 3]
*
* unapply(Array.from)(1, 2, 3)
* // => [1, 2, 3]
*/
const unapply = f => (...args) => f(args)
/**
* Creates a function with inverted argument order.
* @param {Function} f Function to flip.
* @return {Function} Flipped function.
* @lodash flip
* @ramda flip
* @example
*
* const join = (...args) => args.join(',')
*
* join(1, 2, 3)
* // => '1,2,3'
*
* flip(join)(1, 2, 3)
* // => '3,2,1'
*/
const flip = f => (...args) => f(...args.reverse())
/**
* Creates a function that maps over `fns` functions passing all its arguments.
* @param {Array<Function>} fns List of functions to apply.
* @return {Function} Function that maps over `fns`.
* @lodash over
* @ramda juxt
* @example
* const range = over([Math.min, Math.max])
*
* range(1,2,3,4,5)
* // => [1,5]
*/
const over = fns => (...args) => fns.map(f => f(...args))
// REDUCE
/// /////////////////////////////////////////////////////////////////////////////
/**
* A more functional reduce implementation.
*
* Simpler alternative: `(f, acc, xs) => xs.reduce(f, acc)`
* @param {Function} f Iterator function.
* @param {*} acc Initial value.
* @param {Array} xs Collection to reduce.
* @return {*} Reduced value.
* @lodash reduce
* @lamda reduce
* @example
*
* const sum = curry(reduce)((acc, el) => acc + el, 0)
*
* sum([1, 2, 3])
* // => 6
*/
const reduce = (f, acc, xs) =>
xs.length ? reduce(f, f(acc, xs[0]), xs.slice(1)) : acc
/**
* A more functional reduceRight implementation.
*
* NOTE: this implementation is similar to Ramda's, which iterator is called
* with the `(value, acc)` signature while `Array#reduceRight` and
* `lodash.reduceRight` keep the order from `reduce` as `(acc, value)`.
*
* Simpler alternative: `(f, acc, xs) => xs.reduceRight(f, acc)`
* @param {Function} f Iterator function.
* @param {*} acc Initial value.
* @param {Array} xs Collection to reduce.
* @return {*} Reduced value.
* @lodash reduceRight
* @lamda reduceRight
* @example
*
* const flatten = curry(reduceR)((el, acc) => acc.concat(el), [])
*
* flatten([[0, 1], [2, 3], [4, 5]])
* // => [4, 5, 2, 3, 0, 1]
*/
const reduceR = (f, acc, xs) =>
xs.length ? f(xs[0], reduceR(f, acc, xs.slice(1))) : acc
// MAP
/// /////////////////////////////////////////////////////////////////////////////
/**
* A more functional Array#map implementation.
*
* Simpler alternative: `(f, xs) => xs.map(f)`
*
* Alternative in terms of reduce:
* `(f, xs) => reduce((acc, el) => [...acc, f(el)], [], xs)`
* @param {Function} f Iterator function.
* @param {Array} xs Array to map.
* @return {Array} New array with mapped values.
* @lodash map
* @ramda map
* @example
*
* const double = n => n * 2
*
* map(double, [1, 2, 3])
* // => [2, 4, 6]
*/
const map = (f, xs) => (xs.length ? [f(xs[0]), ...map(f, xs.slice(1))] : xs)
// OTHER LIST OPERATIONS
/// /////////////////////////////////////////////////////////////////////////////
/**
* A more functional Array#filter implementation in terms of `reduce`.
*
* Simpler alternative: `(f, xs) => xs.filter(f)`
* @param {Function} f Iterator function.
* @param {Array} xs Array to filter.
* @return {Array} Filtered array.
* @lodash filter
* @ramda filter
* @example
*
* const isEven = n => n % 2 === 0
*
* filter(isEven, [1, 2, 3, 4, 5])
* // => [2, 4]
*/
const filter = (f, xs) => xs.reduce((acc, x) => (f(x) ? [...acc, x] : acc), [])
/**
* A more functional Array#find implementation.
*
* Simpler alternative: `(f, xs) => xs.find(f)`
* @param {Function} f Iterator function.
* @param {Array} xs Array to search in.
* @return {*} First element that matches `f` criteria.
* @lodash filter
* @ramda filter
* @example
*
* const isOdd = n => n % 2 !== 0
*
* find(isOdd, [1,3,5,7,9,10,11])
* // => 10
*/
const find = (f, xs) =>
xs.length ? (f(xs[0]) ? xs[0] : find(f, xs.slice(1))) : void 0
/**
* A recursive, more functional, inmmutable implementation of Array#reverse.
*
* Simpler alternative: `xs => xs.slice().reverse()`
* @param {Array} xs Array to reverse.
* @return {Array} Reversed array.
* @lodash reverse
* @lamda reverse
* @example
*
* reverse([1, 2, 3])
* // => [3, 2, 1]
*/
const reverse = ([x, ...xs]) => (xs.length ? [...reverse(xs), x] : [x])
/**
* Filters out duplicated items from the array.
* @param {Array} xs Array to consider.
* @return {Array} Duplicate-free array.
* @lodash uniq
* @ramda uniq
* @example
*
* uniq([1, 2, 3, 4, 4, 5, 6, 3, 2, 7, 8, 9])
* // => [1, 2, 3, 4, 5, 6, 7, 8, 9]
*/
const uniq = xs => [...new Set(xs)]
/**
* Deep flatten an array.
* @param {Array} xs Array to flatten.
* @return {Array} Flat array.
* @lodash flattenDeep
* @ramda flatten
* @example
*
* flat([1, [2, [3, [4]], 5]])
* // => [1, 2, 3, 4, 5]
*/
const flat = xs =>
xs.reduce((acc, x) => acc.concat(Array.isArray(x) ? flat(x) : x), [])
/**
* Splits an array in chunks of size `n`.
* @param {number} n Size of chunks.
* @param {Array} xs Array to chunk.
* @return {Array<Array>} Array with chunks.
* @lodash chunk
* @ramda ?
* @example
*
* chunk(3, [1, 2, 3, 4, 5, 6, 7, 8, 9])
* // => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
*/
const chunk = (n, xs) =>
xs.length > n ? [xs.slice(0, n), ...chunk(n, xs.slice(n))] : [xs]
/**
* Group elements together by their index.
* @param {Array<Array>} xss Arrays to zip.
* @return {Array<Array>} Zipped array.
* @lodash zip
* @ramda zip
* @example
*
* zip([[1, 2, 3], ['a', 'b', 'c']])
* // => [[1, 'a'], [2, 'b'], [3, 'c']]
*
* zip([
* [ 1, 2, 3 ],
* [ 4, 5, 6 ],
* [ 7, 8, 9 ]
* ])
* // => [
* // [ 1, 4, 7 ],
* // [ 2, 5, 8 ],
* // [ 3, 6, 9 ]
* // ]
*
* zip([
* [ 1, 4, 7 ],
* [ 2, 5, 8 ],
* [ 3, 6, 9 ]
* ])
* // => [
* // [ 1, 2, 3 ],
* // [ 4, 5, 6 ],
* // [ 7, 8, 9 ]
* // ]
*/
const zip = xss =>
xss
.reduce((a, b) => (a.length > b.length ? a : b))
.map((_, i) => xss.map(x => x[i]))
// SETS
/// /////////////////////////////////////////////////////////////////////////////
/**
* Creates a new set of values from all given arrays: `A ∪ B ∪ C`
* @param {Array<Array>} xss Arrays to combine.
* @return {Array} Union of the sets.
* @lodash union
* @ramda union
* @example
*
* union([[1, 2, 3], [3, 4, 5], [5, 6, 7]])
* // => [1, 2, 3, 4, 5, 6, 7]
*/
const union = xss => [...new Set(xss.reduce((a, b) => [...a, ...b], []))]
/**
* Get the relative complement of `b` in `a`, that's it, return all elements of
* `a` not present in b: `B ∖ A`
* @param {Array} a Array to check.
* @param {Array} b Set to exclude.
* @return {Array} Array of elements in a not present in b.
* @lodash difference
* @ramda difference
* @example
*
* difference([1, 2, 3, 4], [2, 3, 7])
* // => [1, 4]
*/
const difference = (a, b) => a.filter(x => !b.includes(x))
/**
* Get the intersection of the given sets, that's it, get the elements common to
* all arrays.
* @param {Array} a Array a
* @param {...Array} b Array b
* @return {Array} Intersection of arrays.
*/
const intersection = ([a, ...b]) => a.filter(x => b.every(y => y.includes(x)))
const xor = xss =>
xss.reduce((a, b) => union(difference(a, b), difference(b, a)), [])
// BONUS
/// /////////////////////////////////////////////////////////////////////////////
/**
* Create an new deferred promise that can be resolved/rejected from outside.
* @return {Promise} A new Promise with two extra methods: resolve and reject.
*
* @example
* const unknownResult = () => {
* const deferredPromise = defer()
*
* const errorTimeoutId = setTimeout(
* () => {
* clearTimeout(successTimeoutId)
* deferredPromise.reject(new Error('Error!'))
* },
* Math.round(Math.random() * 1e4)
* )
*
* const successTimeoutId = setTimeout(
* () => {
* clearTimeout(errorTimeoutId)
* deferredPromise.resolve('Success!')
* },
* Math.round(Math.random() * 1e4)
* )
*
* return deferredPromise
* }
*
* unknownResult()
* .then(console.log)
* .catch(console.error)
*/
const defer = (bag = {}) =>
Object.assign(
new Promise((resolve, reject) => Object.assign(bag, { resolve, reject })),
bag
)
const seq = (start, end, step = 1) =>
Array.from({ length: (end - start) / step + 1 }, (_, i) => start + i * step)
/**
* Splits a list into two groups based on `f` predicate. The first group
* contains those items which `f` returns true for, the second contains the
* rest.
* @param {Function} f Predicate.
* @param {Array} xs Array to split.
* @return {Array<Array>} Array containing the two groups.
* @lodash partition
* @ramda partition
* @example
*
* const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
* const isEven = n => n % 2 === 0
*
* partition(isEven, numbers)
* // => [[2, 4, 6, 8], [1, 3, 5, 7, 9]]
*/
const partition = (f, xs) =>
xs.reduce(([a, b], x) => (f(x) ? [[...a, x], b] : [a, [...b, x]]), [[], []])
/**
* Takes elements from `xs` until `f` predicate returns false.
* @param {Function} f Predicate function.
* @param {Array} xs Array to take from.
* @param {Number} [i=0] Where to start at.
* @return {Array} Subset of `xs`.
* @lodash takeWhile
* @ramda takeWhile
* @example
*
* const numbers = [8, 12, 6, 10, 24, 16, 9, 20, 4, 26]
* const isEven = n => n % 2 === 0
*
* takeWhile(isEven, numbers)
* // => [8, 12, 6, 10, 24, 16]
*/
const takeWhile = (f, xs, i = 0) =>
i < xs.length && f(xs[i]) ? takeWhile(f, xs, i + 1) : xs.slice(0, i)
/**
* Just like takeWhile, but from right to left — takes elements from `xs`,
* starting from the right, until `f` predicate returns false.
* @param {Function} f Predicate function.
* @param {Array} xs Array to take from.
* @return {Array} Subset of `xs`.
* @lodash takeRightWhile
* @ramda takeLastWhile
* @example
*
* const numbers = [8, 12, 6, 10, 24, 16, 9, 20, 4, 26]
* const isEven = n => n % 2 === 0
*
* takeWhileR(isEven, numbers)
* // => [20, 4, 26]
*/
const takeWhileR = (f, xs, i = xs.length) =>
i && f(xs[i - 1]) ? takeWhileR(f, xs, i - 1) : xs.slice(i)
/**
* Generate a list from a seed value.
* @param {Function} f Iterator function.
* @param {*} seed Seed value.
* @return {Array} Generated list.
* @lodash ?
* @ramda unfold
* @example
*
* const countDown = n => unfold(seed => seed ? [seed, seed - 1] : false, n)
*
* countDown(7)
* // => [7, 6, 5, 4, 3, 2, 1]
*
* const countUp = n => unfold(seed => seed <= n ? [seed, seed + 1] : false, 1)
* // => [1, 2, 3, 4, 5, 6, 7]
*/
const unfold = (f, seed, acc = [], next = f(seed)) =>
next ? unfold(f, next[1], [...acc, next[0]]) : acc
/**
* The Y combinator. Allows to create recursive procedures without function
* declarations.
*
* See: https://en.wikipedia.org/wiki/Fixed-point_combinator#Fixed_point_combinators_in_lambda_calculus
* See: https://hackernoon.com/casual-functional-adventures-in-javascript-f2baec6c38de
* @param {Function} f Function that receives itself.
* @return {Function} Fixed function.
* @lodash ?
* @ramda ?
* @example
*
* y(fib => (n, curr = 0, next = 1) => n ? fib(n - 1, next, curr + next) : curr)(7)
* // => 13
*/
const y = f => (g => g(g))(h => f((...args) => h(h)(...args)))
/**
* Create a memoized version of `f`. Prevents calculation of previously
* calculated values.
* @param {Function} f Function to memoize.
* @return {Function} Memoized function.
* @lodash memoize
* @ramda memoize
* @example
*
* let count = 0
*
* const factorial = memoize(n => {
* count += 1
* return n > 1 ? factorial(n - 1) * n : 1)
* })
*
* factorial(5) // => 120
*
* count // => 5
*
* factorial(5) // => 120
*
* count // => 5
*
* factorial(4) // => 24
*
* count // => 5
*
* factorial(7) // => 5040
*
* count // => 7
*/
const memoize = f => {
const memo = Object.create(null)
return (...args) => {
const key = JSON.stringify(args)
memo[key] = key in memo ? memo[key] : f(...args)
return memo[key]
}
}

Table of Contents

one-liners

one-liner functional utilities in JavaScript

Meta

  • author: Stefan Maric <[email protected]>
  • license: Copyright © 2017 Stefan Maric <[email protected]> This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.

partial

Partially applies f to arguments args, that's it, returns a function that invokes f with args prepended to the arguments it receives.

Parameters

  • f Function Function to be partially applied.
  • args any Arguments to partially apply.

Examples

const sum = (a, b) => a + b
const sumTen = partial(sum, 10)

sumTen(5)
// => 15

Returns Function Partially applied function.

partialR

Similar to partial, but arguments applied from right to left: Partially applies f to arguments args, that's it, returns a function that invokes f with args appended to the arguments it receives.

Parameters

  • f Function Function to be partially applied.
  • args any Arguments to partially apply.

Examples

const pretty = partialR(JSON.stringify, null, 2)

pretty({ name: 'John', age: 28, children: [{ name: 'Carlos', age: 8 }] })
// => "{
//   "name": "John",
//   "age": 28,
//   "children": [
//     {
//       "name": "Carlos",
//       "age": 8
//     }
//   ]
// }"

Returns Function Partially applied function.

curry

Automagical currying. Creates a function that invokes f if all expected arguments are passed, or returns a function that expects the rest of the arguments.

Strict curry alternative: f => x => f.length > 1 ? curry(f.bind(null, x)) : f(x)

Parameters

Examples

const foo = (a, b, c) => [a, b, c]

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

const curriedFoo = curry(foo)

curriedFoo(1)
// => Function

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

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

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

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

curriedFoo(1)(undefined)(3)
// => [1, undefined, 3]

Returns Function Curried function.

curryR

Just like curry, but arguments are partially applied from right to left.

Strict curryR alternative: (f, ari = f.length) => x => ari <= 1 ? f(x) : curryR((...args) => f(...args, x), ari - 1)

Alternative using partialR: (f, ari = f.length) => (...args) => args.length >= ari ? f(...args) : curryR(partialR(f, ...args), ari - args.length)

Parameters

  • f Function Function to be curried.
  • ari number The expected arity of f. (optional, default f.length)

Examples

const bar = (a, b, c) => [a, b, c]

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

const curriedBar = curryR(bar)

curriedBar(1)
// => Function

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

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

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

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

curriedBar(1)(undefined)(3)
// => [3, undefined, 1]

Returns Function Curried function.

compose

Compose functions from right to left — creates a function than, when invoked, calls the last function in the array with its arguments, the result of it is passed as argument to the previous function and so on until the value is returned.

Simpler alternative: fns => x => fns.reduceRight((v, f) => f(v), x)

Parameters

  • fns Array<Function> Functions to be composed.
    • fns.0
    • fns.rest ...any

Examples

const reverse = s => s.split('').reverse().join('')
const lowercase = s => s.toLowerCase()
const capitalize = s => s.replace(/\b\w/g, c => c.toUpperCase())
const bang = s => s + '!'

const anadrome = compose([
  bang,
  capitalize,
  lowercase,
  reverse
])

anadrome('Redraw Dog Flow')
// => "Wolf God Warder!"

Returns Function Composition of the functions.

flow

Compose functions from left to right — creates a function that, when invoked, calls the first function in the array with its arguments, the result of it is passed as argument to the next function and so on until the value is returned.

Simpler alternative: fns => x => fns.reduce((v, f) => f(v), x)

Parameters

  • fns Array<Function> Functions to be composed.
    • fns.0
    • fns.rest ...any

Examples

const reverse = s => s.split('').reverse().join('')
const lowercase = s => s.toLowerCase()
const capitalize = s => s.replace(/\b\w/g, c => c.toUpperCase())
const bang = s => s + '!'

const anadrome = flow([
  reverse,
  lowercase,
  capitalize,
  bang
])

anadrome('Redraw Dog Flow')
// => "Wolf God Warder!"

Returns Function Composition of the functions.

asyncFlow

Compose async and sync operations seamless.

Parameters

Examples

const wait = (ms, x) => new Promise(resolve => setTimeout(() => resolve(x), ms))

const doStuff = asyncFlow([
  async n => (n + await wait(1000, 4)) * await wait(2000, 6),
  n => n % 10,
  async n => n / await wait(1000, 2)
])

doStuff(Promise.resolve(7))
  .then(console.log)
  .catch(console.error)
// => 3

Returns Function Composition of the functions.

apply

Creates a function that takes an array of arguments args and inkokes f passing each item of args as function arguments, similar to Function#apply.

This allows to convert a variadic or n-ary function into an unary function.

Parameters

  • f Function Variadic/n-ary function to apply.

Examples

Math.max(1, 2, 3)
// => 3

apply(Math.max)([1,2,3])
// => 3

Returns Function Converted unary function.

unapply

The opposite of apply — creates a function that takes any number of arguments and invokes f passing them as an array.

Parameters

Examples

Array.from([1, 2, 3])
// => [1, 2, 3]

unapply(Array.from)(1, 2, 3)
// => [1, 2, 3]

Returns Function Converted variadic function.

flip

Creates a function with inverted argument order.

Parameters

Examples

const join = (...args) => args.join(',')

join(1, 2, 3)
// => '1,2,3'

flip(join)(1, 2, 3)
// => '3,2,1'

Returns Function Flipped function.

over

Creates a function that maps over fns functions passing all its arguments.

Parameters

Examples

const range = over([Math.min, Math.max])

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

Returns Function Function that maps over fns.

reduce

A more functional reduce implementation.

Simpler alternative: (f, acc, xs) => xs.reduce(f, acc)

Parameters

  • f Function Iterator function.
  • acc any Initial value.
  • xs Array Collection to reduce.

Examples

const sum = curry(reduce)((acc, el) => acc + el, 0)

sum([1, 2, 3])
// => 6

Returns any Reduced value.

reduceR

A more functional reduceRight implementation.

NOTE: this implementation is similar to Ramda's, which iterator is called with the (value, acc) signature while Array#reduceRight and lodash.reduceRight keep the order from reduce as (acc, value).

Simpler alternative: (f, acc, xs) => xs.reduceRight(f, acc)

Parameters

  • f Function Iterator function.
  • acc any Initial value.
  • xs Array Collection to reduce.

Examples

const flatten = curry(reduceR)((el, acc) => acc.concat(el), [])

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

Returns any Reduced value.

map

A more functional Array#map implementation.

Simpler alternative: (f, xs) => xs.map(f)

Alternative in terms of reduce: (f, xs) => reduce((acc, el) => [...acc, f(el)], [], xs)

Parameters

Examples

const double = n => n * 2

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

Returns Array New array with mapped values.

filter

A more functional Array#filter implementation in terms of reduce.

Simpler alternative: (f, xs) => xs.filter(f)

Parameters

Examples

const isEven = n => n % 2 === 0

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

Returns Array Filtered array.

find

A more functional Array#find implementation.

Simpler alternative: (f, xs) => xs.find(f)

Parameters

Examples

const isOdd = n => n % 2 !== 0

find(isOdd, [1,3,5,7,9,10,11])
// => 10

Returns any First element that matches f criteria.

reverse

A recursive, more functional, inmmutable implementation of Array#reverse.

Simpler alternative: xs => xs.slice().reverse()

Parameters

  • xs Array Array to reverse.
    • xs.0
    • xs.xs ...any

Examples

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

Returns Array Reversed array.

uniq

Filters out duplicated items from the array.

Parameters

  • xs Array Array to consider.

Examples

uniq([1, 2, 3, 4, 4, 5, 6, 3, 2, 7, 8, 9])
// => [1, 2, 3, 4, 5, 6, 7, 8, 9]

Returns Array Duplicate-free array.

flat

Deep flatten an array.

Parameters

  • xs Array Array to flatten.

Examples

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

Returns Array Flat array.

chunk

Splits an array in chunks of size n.

Parameters

Examples

chunk(3, [1, 2, 3, 4, 5, 6, 7, 8, 9])
// => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Returns Array<Array> Array with chunks.

zip

Group elements together by their index.

Parameters

Examples

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

zip([
  [ 1, 2, 3 ],
  [ 4, 5, 6 ],
  [ 7, 8, 9 ]
])
// => [
//   [ 1, 4, 7 ],
//   [ 2, 5, 8 ],
//   [ 3, 6, 9 ]
// ]

zip([
  [ 1, 4, 7 ],
  [ 2, 5, 8 ],
  [ 3, 6, 9 ]
])
// => [
//   [ 1, 2, 3 ],
//   [ 4, 5, 6 ],
//   [ 7, 8, 9 ]
// ]

Returns Array<Array> Zipped array.

union

Creates a new set of values from all given arrays: A ∪ B ∪ C

Parameters

Examples

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

Returns Array Union of the sets.

difference

Get the relative complement of b in a, that's it, return all elements of a not present in b: B ∖ A

Parameters

Examples

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

Returns Array Array of elements in a not present in b.

intersection

Get the intersection of the given sets, that's it, get the elements common to all arrays.

Parameters

Returns Array Intersection of arrays.

defer

Create an new deferred promise that can be resolved/rejected from outside.

Parameters

  • bag (optional, default {})

Examples

const unknownResult = () => {
  const deferredPromise = defer()

  const errorTimeoutId = setTimeout(
    () => {
      clearTimeout(successTimeoutId)
      deferredPromise.reject(new Error('Error!'))
    },
    Math.round(Math.random() * 1e4)
  )

  const successTimeoutId = setTimeout(
    () => {
      clearTimeout(errorTimeoutId)
      deferredPromise.resolve('Success!')
    },
    Math.round(Math.random() * 1e4)
  )

  return deferredPromise
}

unknownResult()
  .then(console.log)
  .catch(console.error)

Returns Promise A new Promise with two extra methods: resolve and reject.

partition

Splits a list into two groups based on f predicate. The first group contains those items which f returns true for, the second contains the rest.

Parameters

Examples

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
const isEven = n => n % 2 === 0

partition(isEven, numbers)
// => [[2, 4, 6, 8], [1, 3, 5, 7, 9]]

Returns Array<Array> Array containing the two groups.

takeWhile

Takes elements from xs until f predicate returns false.

Parameters

  • f Function Predicate function.
  • xs Array Array to take from.
  • i Number Where to start at. (optional, default 0)

Examples

const numbers = [8, 12, 6, 10, 24, 16, 9, 20, 4, 26]
const isEven = n => n % 2 === 0

takeWhile(isEven, numbers)
// => [8, 12, 6, 10, 24, 16]

Returns Array Subset of xs.

takeWhileR

Just like takeWhile, but from right to left — takes elements from xs, starting from the right, until f predicate returns false.

Parameters

  • f Function Predicate function.
  • xs Array Array to take from.
  • i (optional, default xs.length)

Examples

const numbers = [8, 12, 6, 10, 24, 16, 9, 20, 4, 26]
const isEven = n => n % 2 === 0

takeWhileR(isEven, numbers)
// => [20, 4, 26]

Returns Array Subset of xs.

unfold

Generate a list from a seed value.

Parameters

  • f Function Iterator function.
  • seed any Seed value.
  • acc (optional, default [])
  • next (optional, default f(seed))

Examples

const countDown = n => unfold(seed => seed ? [seed, seed - 1] : false, n)

countDown(7)
// => [7, 6, 5, 4, 3, 2, 1]

const countUp = n => unfold(seed => seed <= n ? [seed, seed + 1] : false, 1)
// => [1, 2, 3, 4, 5, 6, 7]

Returns Array Generated list.

y

The Y combinator. Allows to create recursive procedures without function declarations.

See: https://en.wikipedia.org/wiki/Fixed-point_combinator#Fixed_point_combinators_in_lambda_calculus See: https://hackernoon.com/casual-functional-adventures-in-javascript-f2baec6c38de

Parameters

  • f Function Function that receives itself.

Examples

y(fib => (n, curr = 0, next = 1) => n ? fib(n - 1, next, curr + next) : curr)(7)
// => 13

Returns Function Fixed function.

memoize

Create a memoized version of f. Prevents calculation of previously calculated values.

Parameters

Examples

let count = 0

const factorial = memoize(n => {
  count += 1
  return n > 1 ? factorial(n - 1) * n : 1)
})

factorial(5) // => 120

count // => 5

factorial(5) // => 120

count // => 5

factorial(4) // => 24

count // => 5

factorial(7) // => 5040

count // => 7

Returns Function Memoized function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment