meows
Version:
A kittybin of tools.
179 lines (177 loc) • 6.44 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
const types_2 = require("./types");
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Functions
/**
* Takes a sequence of functions and composes them from right to left, where
* given an array of functions such as `[λa, λb, λc]`, return a new function
* `(...args) => λa(λb(λc(...args)))`.
*
* @example
* let add2 = x => x + 2
* let mul3 = x => x * 3
* let sub1 = x => x - 1
* let λa = compose(add2, mul3) // add2(mul3(...a))
* let λb = compose(mul3, add2) // mul3(add2(...a))
* let λc = compose(add2, mul3, sub1) // add2(mul3(sub1(...a)))
* λa(0) // => 2
* λb(0) // => 6
* λc(0) // => -1
*/
exports.compose = (...fns) => fns.reduce((f, g) => (...a) => f(g(...a)));
/**
* Takes a sequence of functions and composes them from left to right, where
* given an array of functions such as `[λa, λb, λc]`, return a new function
* `(...args) => λc(λb(λa(...args)))`.
*
* @example
* let add2 = x => x + 2
* let mul3 = x => x * 3
* let sub1 = x => x - 1
* let λa = pipe(add2, mul3) // mul3(add2(...a))
* let λb = pipe(mul3, add2) // add2(mul3(...a))
* let λc = pipe(add2, mul3, sub1) // sub1(mul3(add2(...a)))
* λa(0) // => 6
* λb(0) // => 2
* λc(0) // => 5
*/
exports.pipe = (...fns) => fns.reduceRight((f, g) => (...a) => f(g(...a)));
/**
* Given a lambda `λ` and two arrays of non-zero length, `arr1` and `arr2`,
* creates new list where each new value indexed at position `i` corresponds to
* the result of `λ(arr1[i], arr2[i])`.
*
* @example
* const triangles = [0, 1, 3, 6, 10]
* const squares = [0, 1, 4, 9, 16]
* const naturals = [0, 1, 2, 3]
* const minus = (a, b) => a - b
*
* zipWith(minus, squares, triangles) // => [0, 0, 1, 3, 6]
* zipWith(minus, triangles, naturals) // => [0, 0, 1, 3]
* zipWith(minus, [], triangles) // => []
*/
function zipWith(λ, arr1, arr2) {
if (arr1.length === 0 || arr2.length === 0)
return [];
return [λ(arr1[0], arr2[0])].concat(zipWith(λ, arr1.slice(1), arr2.slice(1)));
}
exports.zipWith = zipWith;
/**
* @example
* [true, true, false].filter(identity) // => [true, true]
*/
exports.identity = x => x;
/**
* Given a lambda and a value `x`, run the lambda with optional arguments as a
* side effect, and then return the original given value `x`.
*
* @example
* function sayHi(msg = 'hi') { console.log(msg) }
* effectId(sayHi, 42) // => logs 'hi' and returns 42
* effectId(sayHi, 42, 'yo') // => logs 'yo' and returns 42
*/
function effectId(λ, x, ...arg) {
if (typeof λ !== 'function')
throw new TypeError(`effectId() requires a function, but got: ${λ}.`);
λ(...arg);
return x;
}
exports.effectId = effectId;
/**
* Count the number of array elements which pass a boolean function.
* @example
* const v1 = [2, 4, 6, 8]
* const v2 = [2, 4, 6, 9]
* const even = x => x % 2 === 0
*
* count(even, v1) // => 4
* count(even, v2) // => 1
* count(even, []) // => 0
*/
function count(λ, arr) {
if (!Array.isArray(arr))
throw new TypeError(`count(λ, arr) requires an array, but got: ${arr}.`);
if (typeof λ !== 'function')
throw new TypeError(`count(λ, arr) requires a function, but got: ${λ}.`);
return arr.filter(λ).length;
}
exports.count = count;
/**
* Given λ → returns `(compose not λ)`.
* @example
* const notArray = not(Array.isArray)
* notArray([]) // => false
* notArray({}) // => true
*/
exports.not = (λ) => (...arg) => !λ(...arg);
/**
* Checks if an array is a **non-empty array** of items that would all pass your
* boolean function. This is unlike the normal convention to pass on empty
* arrays.
*
* @example
* always(even, [0, 2, 4]) // => true
* always(even, [0, 1, 2]) // => false
* always(even, []) // => false, fails on empty array!
*/
function always(λ, arr) {
if (!Array.isArray(arr) || typeof λ !== 'function')
throw new TypeError(`always() requires a boolean function and array of arguments, but got:`
+ `\n arg[0]: ${types_1.typeName(λ)}`
+ `\n arg[1]: ${types_1.typeName(arr)}`);
return Boolean(arr.length) && arr.every(λ);
}
exports.always = always;
/**
* Coerces values to `true`. If input is a function, `truthy` will run the
* function with optional arguments for side effects before returning `true`.
* @example
* truthy(console.log('hi')) && 42 // logs 'hi' and returns 42
*
* const say = (msg='hi') => console.log(msg)
* truthy(say) // logs 'hi' and returns true
* truthy(say, 'yo') // logs 'yo' and returns true
*/
function truthy(λ, ...arg) {
typeof λ === 'function' && void λ(...arg);
return true;
}
exports.truthy = truthy;
/**
* Coerces values to `false`. If input is a function, `falsy` will run the
* function with optional arguments for side effects before returning `false`
*
* @example
* falsy(console.log('hi')) || 42 // logs 'hi' and returns 42
*
* function say(msg='hi') { console.log(msg) }
* falsy(say) // logs 'hi' and returns false
* falsy(say, 'yo') // logs 'yo' and returns false
*/
function falsy(λ, ...arg) {
typeof λ === 'function' && void λ(...arg);
return false;
}
exports.falsy = falsy;
/**
* Checks if a non-empty array of functions all pass another non-empty array of
* values. Returns `false` on any empty array.
*
* @example
* allPassAll([isEven, isPositive, isSafe], [2, 4, 6, 8]) // => true
*/
function allPassAll(tests, values, throwOnEmpty = false) {
if (!tests.every(types_2.isType.fn))
throw new TypeError(`allPassAll() requires an array of only functions, but got: ${tests}.`);
if (!always(Array.isArray, [tests, values]))
throw new TypeError(`allPassAll() requires two arrays, but got: ${tests}, ${values}.`);
if (throwOnEmpty && [tests, values].some(types_1.isEmpty.arr))
throw new TypeError(`allPassAll() rejects empty arrays due to optional flag, but got: ${tests}, ${values}.`);
return [tests, values].some(types_1.isEmpty.arr)
? false
: values.every(val => tests.every(λ => λ(val)));
}
exports.allPassAll = allPassAll;