UNPKG

@yagni-js/yagni

Version:

Yet another functional library

1,870 lines (1,760 loc) 73.9 kB
/** * * @module @yagni-js/yagni * @version 0.7.1 * @author Yuri Egorov <ysegorov at gmail dot com> * @license Unlicense http://unlicense.org * */ /** * Takes three functions `predicate`, `fnIf` and `fnElse` as an arguments * and returns **a new function**, which then takes `smth` as an argument * and returns **the result of calling either `fnIf` or `fnElse`** function * depending on the result of calling `predicate` function. * * @category Logic * * @param {Function} predicate function to check a condition * @param {Function} fnIf function to call if `predicate` evaluates to true * @param {Function} fnElse function to call if `predicate` evaluates to false * @returns {Function} a new function to take `smth` as an argument and return * the result of calling either `fnIf(smth)` or `fnElse(smth)` function * depending on the `predicate(smth)` call result * * @example * * import {ifElse} from '@yagni-js/yagni'; * * function isNil(smth) { return smth === void 0 || smth === null; } * function ifDefined(smth) { return smth; } * function ifUndefined(smth) { return 'foo'; } * * const valueOrFoo = ifElse(isNil, ifUndefined, ifDefined); * * const res0 = valueOrFoo(null); // => 'foo' * const res1 = valueOrFoo('baz'); // => 'baz' * const res2 = valueOrFoo('bar'); // => 'bar' * * // same thing using only `@yagni-js/yagni` functions * * import {isNil, identity, always, ifElse} from '@yagni-js/yagni'; * * const valueOrFoo = ifElse(isNil, always('foo'), identity); * * const res0 = valueOrFoo(null); // => 'foo' * const res1 = valueOrFoo('baz'); // => 'baz' * const res2 = valueOrFoo('bar'); // => 'bar' * */ function ifElse(predicate, fnIf, fnElse) { return function _ifElse(smth) { return predicate(smth) ? fnIf(smth) : fnElse(smth); }; } /** * Takes two functions `left` and `right` as arguments and returns * **a new function**, which then takes some value `smth` as an argument * and returns **boolean** - `true` if both `left(smth)` and `right(smth)` * calls return true and `false` otherwise * * @category Logic * * @param {Function} left predicate function to calculate left side value * @param {Function} right predicate function to calculate right side value * @returns {Function} a new function to take `smth` as an argument and * return true if both `left(smth)` and `right(smth)` evaluate to true and * false otherwise * * @example * * import {and} from '@yagni-js/yagni'; * * function above0(smth) { return smth > 0; } * function below100(smth) { return smth < 100; } * * const test = and(above0, below100); * * const res0 = test(42); // => true * const res1 = test(-1); // => false * const res2 = test(101); // => false * */ function and(left, right) { return function _and(smth) { return left(smth) && right(smth); }; } /** * Takes two functions `left` and `right` as arguments and returns * **a new function**, which then takes some value `smth` as an argument * and returns **boolean** - `true` if `left(smth)` or `right(smth)` * calls return true and `false` otherwise * * @category Logic * * @param {Function} left predicate function to calculate left side value * @param {Function} right predicate function to calculate right side value * @returns {Function} a new function to take `smth` as an argument and * return true if `left(smth)` or `right(smth)` evaluates to true and * false otherwise * * @example * * import {or} from '@yagni-js/yagni'; * * function below0(smth) { return smth < 0; } * function above100(smth) { return smth > 100; } * * const test = or(below0, above100); * * const res0 = test(42); // => false * const res1 = test(-1); // => true * const res2 = test(101); // => true * */ function or(left, right) { return function _or(smth) { return left(smth) || right(smth); }; } /** * Takes a function `test` and returns **a new function**, which then * takes some value `smth` as an argument and returns **negated result** of the * call `test(smth)`. * * @category Logic * * @param {Function} test predicate function * @returns {Function} a new function to take `smth` as an argument and return * `!` of the call `test(smth)` * * @example * * import {not} from '@yagni-js/yagni'; * * function equalsFoo(smth) { return smth === 'foo'; } * * const notEqualsFoo = not(equalsFoo); * * const res0 = notEqualsFoo('foo'); // => false * const res1 = notEqualsFoo('baz'); // => true * const res2 = notEqualsFoo('bar'); // => true * */ function not(test) { return function _not(smth) { return !test(smth); }; } /** * Takes some value `smth` as an argument and returns **boolean** - * `true` if `smth` is `null` or `undefined`, `false` otherwise. * * @category Test * * @param {*} smth value to test * @returns {Boolean} true if `smth` is `null` or `undefined`, `false` otherwise * * @example * * import {isNil} from '@yagni-js/yagni'; * * var a; * * const res0 = isNil(null); // => true * const res1 = isNil(a); // => true * const res2 = isNil('foo'); // => false * const res3 = isNil(42); // => false * const res4 = isNil({}); // => false * const res5 = isNil([]); // => false * */ function isNil(smth) { return (smth === null) || (smth === void 0); } /** * Takes some value `smth` as an argument and returns **boolean** - * `true` if `smth` is not `null` and not `undefined`, `false` otherwise. * * @category Test * * @param {*} smth value to test * @returns {Boolean} true if `smth` is not `null` and not `undefined`, * `false` otherwise * * @example * * import {isDefined} from '@yagni-js/yagni'; * * var a; * * const res0 = isDefined(null); // => false * const res1 = isDefined(a); // => false * const res2 = isDefined('foo'); // => true * const res3 = isDefined(42); // => true * const res4 = isDefined({}); // => true * const res5 = isDefined([]); // => true * */ function isDefined(smth) { return !isNil(smth); } /** * Takes some value `smth` as an argument and returns **boolean** - * `true` if `smth` is an array, `false` otherwise. * * Delegates to `Array.isArray` method. * * @category Test * * @param {*} smth value to test * @returns {Boolean} true if `smth` is an array, false otherwise * * @example * * import {isArray} from '@yagni-js/yagni'; * * const res0 = isArray([]); // => true * const res1 = isArray({}); // => false * const res2 = isArray(42); // => false * */ function isArray(smth) { return Array.isArray(smth); } /** * Takes some value `smth` as an argument and returns **boolean** - * `true` if `smth` is an instance of Object, `false` otherwise. * * @category Test * * @param {*} smth value to test * @returns {Boolean} true if `smth` is an instance of Object, false otherwise * * @example * * import {isObject} from '@yagni-js/yagni'; * * const res0 = isObject({}); // => true * const res1 = isObject([]); // => false * const res2 = isObject(42); // => false * */ function isObject(smth) { return Object.prototype.toString.call(smth) === '[object Object]'; } /** * Takes some value `smth` as an argument and returns **boolean** - * `true` if `smth` is a string, `false` otherwise. * * @category Test * * @param {*} smth value to test * @returns {Boolean} true if `smth` is a string, false otherwise * * @example * * import {isString} from '@yagni-js/yagni'; * * const res0 = isString('foo'); // => true * const res1 = isString([]); // => false * const res2 = isString(42); // => false * */ function isString(smth) { return Object.prototype.toString.call(smth) === '[object String]'; } /** * Takes some value `smth` as an argument and returns **boolean** - * `true` if `smth` is a function, `false` otherwise. * * @category Test * * @param {*} smth value to test * @returns {Boolean} true if `smth` is a function, false otherwise * * @example * * import {isFunction} from '@yagni-js/yagni'; * * function foo() { return 'baz'; } * * const res0 = isFunction(foo); // => true * const res1 = isFunction([]); // => false * const res2 = isFunction(42); // => false * */ function isFunction(smth) { return Object.prototype.toString.call(smth) === '[object Function]'; } /** * Takes some value `smth` as an argument and returns **boolean** * `true` if `smth` strictly equals to `true`, `false` otherwise. * * @category Test * * @param {*} smth value to test * @returns {Boolean} true if `smth` strictly equals to `true`, false otherwise * * @example * * import {isTrue} from '@yagni-js/yagni'; * * const foo = true; * const baz = 42; * const bar = false; * * const res0 = isTrue(foo); // => true * const res1 = isTrue(baz); // => false * const res2 = isTrue(bar); // => false * */ function isTrue(smth) { return smth === true; } /** * Takes some value `smth` as an argument and returns **boolean** * `true` if `smth` strictly equals to `false`, `false` otherwise. * * @category Test * * @param {*} smth value to test * @returns {Boolean} true if `smth` strictly equals to `false`, false otherwise * * @example * * import {isFalse} from '@yagni-js/yagni'; * * const foo = false; * const baz = 42; * const bar = true; * * const res0 = isFalse(foo); // => true * const res1 = isFalse(baz); // => false * const res2 = isFalse(bar); // => false * */ function isFalse(smth) { return smth === false; } /** * Takes some value `left` as an argument and returns **a new function**, * which then takes some value `right` as an argument and returns **boolean** * as a result of strict comparision of `left` and `right`. * * @category Test * * @param {*} left some value to check equality to * @returns {Function} a new function to take `right` as an argument and * return true if `left` is strictly equal to `right` or false otherwise * * @example * * import {equals} from '@yagni-js/yagni'; * * const isZero = equals(0); * * const res0 = isZero(0); // => true * const res1 = isZero(42); // => false * */ function equals(left) { return function _equals(right) { return left === right; }; } /** * Takes a function `sideEffect` as an argument and returns **a new function**, * which then takes some value `smth` as an argument, calls `sideEffect(smth)` * throwing away it's result and returns back **`smth`**. * * @category Impure * * @param {Function} sideEffect function to call * @returns {Function} a new function to take `smth` as an argument, perform * a call `sideEffect(smth)` throwing away it's result and return `smth` * * @example * * import {tap} from '@yagni-js/yagni'; * * const log = tap(console.log); * * const res0 = log('foo'); // => 'foo', logs to console 'foo' * const res1 = log(42); // => 42, logs to console 42 * */ function tap(sideEffect) { return function (smth) { // NB. side effect // eslint-disable-next-line fp/no-unused-expression const r = sideEffect(smth); return smth; }; } /** * Takes object, attribute name and new value for the attribute as arguments, * performs mutation and returns same object. * * @category Impure * * @param {Object} subj object to mutate * @param {String} attr attribute name * @param {*} value new value to assign to the attribute * @returns {Object} source object `subj` * * @example * * import {mutate} from '@yagni-js/yagni'; * * var a = {}; * * var b = mutate(a, 'foo', 'baz'); // => {foo: 'baz'}, a === b * var c = mutate(b, 'bar', 42); // => {foo: 'baz', bar: 42}, b === c * */ function mutate(subj, attr, value) { // eslint-disable-next-line fp/no-mutation subj[attr] = value; return subj; } /** * Takes `key` and `value` as arguments and returns **a new object**. * * Uses `mutate` to mutate newly created empty object. * * @category Object * * @param {String} key key name * @param {*} value key value * @returns {Object} a new object, which has only one key * * @see mutate * * @example * * import {obj} from '@yagni-js/yagni'; * * const o1 = obj('foo', 'baz'); // => {foo: 'baz'} * const o2 = obj({'bar', 42); // => {bar: 42} * */ function obj(key, value) { return mutate({}, key, value); } /** * Takes `key` as an argument and returns **a new function**, which then takes * some `value` as an argument and returns **a new object**. * * @category Object * * @param {String} key key name * @returns {Function} a new function to take `value` as an argument and return * a new object * * @see obj * * @example * * import {objOf} from '@yagni-js/yagni'; * * const fooObj = objOf('foo'); * * const o1 = fooObj('baz'); // => {foo: 'baz'} * const o2 = fooObj('bar'); // => {foo: 'bar'} * const o3 = fooObj(42); // => {foo: 42} * */ function objOf(key) { return function (value) { return obj(key, value); }; } /** * Takes an object `a` as an argument and returns **a new function**, * which then takes an object `b` as an argument and returns **a new object** * as a result of merging `a` and `b`. * * Uses `Object.assign` method. Keeps source objects `a` and `b` untouched. * * @category Object * * @param {Object} a * @returns {Function} a new function to take an object `b` as an argument * and return a new object as a result of merging `a` and `b` * * @example * * import {merge} from '@yagni-js/yagni'; * * const a = {foo: 'baz'}; * const b = {bar: 42}; * const mergeA = merge(a); * * const res = mergeA(b); * // => {foo: 'baz', bar: 42}, res !== a, res !== b * */ function merge(a) { return function (b) { return Object.assign({}, a, b); }; } /** * Takes `key` name as an argument and returns **a new function**, which then * takes some object `smth` as an argument and returns **value** taken from * `smth` by `key`. * * @category Object * * @param {String} key key name * @returns {Function} a new function to take `smth` as an argument and * return value taken from `smth` by `key` * * @see pickFrom * @see pickPath * * @example * * import {pick} from '@yagni-js/yagni'; * * const src = {foo: 12, baz: 42, bar: 80}; * const pickFoo = pick('foo'); * const pickBaz = pick('baz'); * const pickBar = pick('bar'); * * const foo = pickFoo(src); // => 12 * const baz = pickBaz(src); // => 42 * const bar = pickBar(src); // => 80 * */ function pick(key) { return function (smth) { return smth[key]; }; } /** * Takes some object `smth` as an argument and returns **a new function**, * which then takes `key` name as an argument and returns **value** taken from * `smth` by `key`. * * @category Object * * @param {Object} smth source object * @returns {Function} a new function to take `key` name as an argument and * return value taken from `smth` by `key` * * @see pick * @see pickPath * * @example * * import {pickFrom} from '@yagni-js/yagni'; * * const src = {foo: 12, baz: 42, bar: 80}; * const pickSrc = pickFrom(src); * * const foo = pickSrc('foo'); // => 12 * const baz = pickSrc('baz'); // => 42 * const bar = pickSrc('bar'); // => 80 * */ function pickFrom(smth) { return function (key) { return smth[key]; }; } /** * Takes an array of key names chain `arr` as an argument and returns * **a new function**, which then takes an object `smth` and returns * **last value** in chain of keys from `arr` or `undefined`. * * Uses `reduce` method of `arr` argument. * * @category Object * * @param {Array} arr array of key names * @returns {Function} a new function to take some object `smth` * as an argument and return last value in chain of keys from `arr` or * `undefined` * * @see pick * @see pickFrom * * @example * * import {pickPath} from '@yagni-js/yagni'; * * const path = ['foo', 'baz', 'bar']; * const getFooBazBar = pickPath(path); * * const src = { * foo: { * baz: { * bar: 42 * } * } * }; * * const res0 = getFooBazBar(src); // => 42 * const res1 = getFooBazBar({foo: {}}); // => undefined * const res2 = getFooBazBar({}); // => undefined */ function pickPath(arr) { return function (smth) { return arr.reduce(function (acc, key) { return isNil(acc) ? acc : acc[key]; }, smth); }; } /** * Takes key name `key` as an argument and returns **a new function**, * which then takes some object `smth` as an argument and returns * **boolean** - `true` in case object has defined value for `key`, * `false` otherwise. * * @category Object * * @param {String} key key name * @returns {Function} a new function to take some object `smth` as an argument * and return true if `smth` has defined value for `key`, `false` otherwise * * @example * * import {has} from '@yagni-js/yagni'; * * const hasFoo = has('foo'); * const hasBaz = has('baz'); * * const src = {foo: 42}; * * const res0 = hasFoo(src); // => true * const res1 = hasBaz(src); // => false * */ function has(key) { return function (smth) { return isDefined(smth[key]); }; } /** * Takes some object `smth` and returns **an array** of key names. * * Uses `Object.keys` to get the result. * * @category Object * * @param {Object} smth source object * @returns {Array} an array of key names * * @see values * @see items * * @example * * import {keys} from '@yagni-js/yagni'; * * const res = keys({foo: 42, baz: 'bar'}); // => ['foo', 'baz']; * */ function keys(smth) { return Object.keys(smth); } /** * Takes some object `smth` and returns **an array** of values. * * @category Object * * @param {Object} smth source object * @returns {Array} an array of values * * @see keys * @see items * * @example * * import {values} from '@yagni-js/yagni'; * * const res = values({foo: 42, baz: 'bar'}); // => [42, 'bar']; * */ function values(smth) { return keys(smth).map(function (key) { return smth[key]; }); } /** * Takes some object `smth` as an argument and returns **an array** of objects, * each object has correspondent `key` and `value` fields taken from `smth`. * * @category Object * * @param {Object} smth source object * @returns {Array} an array of objects * * @see keys * @see values * * @example * * import {items} from '@yagni-js/yagni'; * * const src = {foo: 42, baz: 'bar'}; * * const res = items(src); * // => * // [ * // {key: 'foo', value: 42}, * // {key: 'baz', value: 'bar'} * // ] * */ function items(smth) { return keys(smth).map(function (key) { return {key: key, value: smth[key]}; }); } /** * Takes an array of key names `arr` and returns **a new function**, * which then takes some object `smth` and returns **a new object**, which * doesn't have specified in `arr` keys. * * @category Object * * @param {Array} arr an array of key names to exclude * @returns {Function} a new function to take some object `smth` * as an argument and return a new object with keys from `arr` excluded * * @example * * import {omit} from '@yagni-js/yagni'; * * const src = {foo: 'foo', baz: 'baz', bar: 'bar'}; * const omitFoo = omit(['foo']); * const omitBazBar = omit(['baz', 'bar']); * * const res0 = omitFoo(src); // => {baz: 'baz', bar: 'bar'} * const res1 = omitBazBar(src); // => {foo: 'foo'} * */ function omit(arr) { return function _omit(smth) { return keys(smth).reduce( function (acc, key) { return arr.indexOf(key) === -1 ? Object.assign({}, acc, obj(key, smth[key])) : acc; }, {} ); }; } /** * Takes a function `mapper` as an argument and returns **a new function**, * which then takes an object `obj` as an argument and returns **a new array**, * produced by applying `mapper` to each `(key, value)` pair of `obj`. * * `mapper` function must have the following signature: * * function mapper(key, value[, idx]) * * Delegates to `map` method of array of `obj` keys. * * @category Object * * @param {Function} mapper function to apply to each `(key, value)` pair * of source object * @returns {Function} a new function to take an object `obj` as an argument * and produce a new array by applying `mapper` to each `(key, value)` pair * of `obj`. * * @see map * * @example * import {mapObj} from '@yagni-js/yagni'; * * function toParam(key, value) { return key + '=' + value; } * const mapper = mapObj(toParam); * * const src = {foo: 'baz', bar: 42}; * * const res = mapper(src); // => ['foo=baz', 'bar=42'] * */ function mapObj(mapper) { return function _mapObj(obj) { return keys(obj).map( function __mapObj(key, idx) { return mapper(key, obj[key], idx); } ); }; } /** * Takes a function `reducer` as an argument and returns **a new function**, * which then takes an object `obj` as an argument and returns * **a new function**, which then takes some `initial` value as an argument * resulting **in a single output value**, produced by executing the specified * `reducer` function on each `(key, value)` pair from `obj`. * * `reducer` function must have the following signature: * * function reducer(accumulator, key, value[, idx]) * * and must return single output value. * * Delegates to `reduce` method of array of `obj` keys. * * @category Object * * @param {Function} reducer function to iteratively apply to each key, value * pair of source object * @returns {Function} a new function to take `obj` as an argument and * produce another new function to take `initial` value and execute * `reducer` function over each `(key, value)` pair of the `obj` * producing a single output value * * @see reduce * * @example * import {reduceObj} from '@yagni-js/yagni'; * * function swap(acc, key, value) { * return Object.assign({}, acc, _.obj(value, key)); * } * const swapper = reduceObj(swap); * const data = {foo: 'baz', bar: 42}; * const swapData = swapper(data); * * const res0 = swapData({}); * // => * // {baz: 'foo', '42': 'bar'} * * const res1 = swapData({lorem: 'ipsum'}); * // => * // {baz: 'foo', '42': 'bar', ipsum: 'lorem'} * */ function reduceObj(reducer) { return function _reduceObj(obj) { return function __reduceObj(initial) { return keys(obj).reduce( function ___reduceObj(acc, key, idx) { return reducer(acc, key, obj[key], idx); }, initial ); }; }; } /** * Takes an array-like object `smth` as input * and returns **a new array**. * * Delegates to `Array.prototype.slice` method call. * * @category Array * * @param {*} smth array-like collection or object * @returns {Array} newly created array * * @example * import {toArray} from '@yagni-js/yagni'; * * const obj = {0: 'foo', 1: 'baz', 2: 'bar', length: 3}; * const arr = toArray(obj); // => ['foo', 'baz', 'bar'] * */ function toArray(smth) { return Array.prototype.slice.call(smth); } /** * Takes an array as input and returns **first element** from it. * * @function * @category Array * * @param {Array} arr source array * @returns {*} first element from array or `undefined` if array is empty * * @example * import {first} from '@yagni-js/yagni; * * const arr = ['foo', 'baz', 'bar']; * const foo = first(arr); // => 'foo' * */ const first = pick(0); /** * Takes an array as input and returns **it's length**. * * @function * @category Array * * @param {Array} arr source array * @returns {Number} length of array * * @example * import {length} from '@yagni-js/yagni'; * * const arr = ['foo', 'baz', 'bar']; * const len = length(arr); // => 3 * */ const length = pick('length'); /** * Takes a function `mapper` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and returns **a new array**, * produced by applying `mapper` to each element of `arr`. * * Delegates to `map` method of `arr` argument. * * @category Array * * @param {Function} mapper function to apply to each element of source array * @returns {Function} a new function to take an array `arr` as an argument * and produce a new array by applying `mapper` to each element of `array` * * @see pipe * @see mapObj * * @example * import {map} from '@yagni-js/yagni'; * * function _add2(x) { return x + 2; } * const add2 = map(_add2); * * const src = [2, 5, 8]; * const dst = add2(src); // => [4, 7, 10] * */ function map(mapper) { return function _map(arr) { return arr.map(mapper); }; } /** * Takes a function `reducer` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and returns * **a new function**, which then takes some `initial` value as an argument * resulting **in a single output value**, produced by executing the specified * `reducer` function on each member of `arr` array. * * `reducer` function must have the following signature: * * function reducer(accumulator, currentValue) * * and must return single output value. * * Delegates to `reduce` method of `arr` argument. * * @category Array * * @param {Function} reducer function to iteratively apply to each element of * source array * @returns {Function} a new function to take `arr` as an argument and * produce another new function to take `initial` value and execute * `reducer` function over each member of the `arr` producing a single * output value * * @see pipe * @see reduceObj * * @example * import {reduce} from '@yagni-js/yagni'; * * function reducer(acc, val) { return acc + val; } * const sum = reduce(reducer); * const values = [2, 5, 8]; * const overall = sum(values); * * const res0 = overall(-15); // => 0 * const res1 = overall(27); // => 42 * const res2 = overall(73); // => 88 * */ function reduce(reducer) { return function _reduce(arr) { return function __reduce(initial) { return arr.reduce(reducer, initial); }; }; } /** * Takes a function `reducer` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and produces **a new array** * by executing the specified `reducer` function over each member * of `arr` array. * * `reducer` function must have the following signature: * * function reducer(accumulator, currentValue) * * and must return an **array**. * * Delegates to `reduce` method of `arr` argument. * * @category Array * * @param {Function} reducer function to iteratively apply to each element of * source array * @returns {Function} a new function to take `array` as an argument and * execute `reducer` function over each member of the `array` producing a new * array as output value * * @see filterMap * @see flatten * @see unique * * @example * import {reduceToArr} from '@yagni-js/yagni'; * * function reducer(acc, val) { return val === 0 ? acc : acc.concat(val); } * const excludeZeros = reduceToArr(reducer); * const values = [2, 0, 5, 8, 0, 1]; * * const res = excludeZeros(values); // => [2, 5, 8, 1] * */ function reduceToArr(reducer) { return function _reduceToArr(arr) { return arr.reduce(reducer, []); }; } /** * Takes an array `arr` of functions as an argument * and returns **a new function**, * which then takes an `initial` value as an argument resulting in **a single * output value**, produced by iteratively executing each function from `array` * over the result of a previous function call. * * This is the **cornerstone** of `@yagni-js/yagni`. * * The idea for such implementation was borrowed from Elixir - you prepare * functions to be executed one after another and trigger the execution by * providing initial value. * * All functions to execute must be unary (must take only one argument) and * must return single output value. * * Delegates to `reduce` method of `arr` argument. * * @function * @category Array * * @param {Array} arr array of functions * @returns {Function} a new function to take `initial` value as an argument * and execute one by one function from source `array` producing a single * output value * * @see reduce * * @example * import {pipe} from '@yagni-js/yagni'; * * function add2(x) { return x + 2; } * function square(x) { return x * x; } * function minus7(x) { return x - 7; } * * const ops = pipe([ * add2, * square, * minus7 * ]); * * const res0 = ops(5); // => 42 * const res1 = ops(6); // => 57 * */ const pipe = reduce( function _pipe(acc, piper) { return piper(acc); } ); /** * Promise-related version of `pipe` function. * * Takes an array `arr` of functions as an argument * and returns **a new function**, * which then takes an `initial` value as an argument resulting in **a single * output value** (an instance of Promise, ie. `thenable`), * produced by iteratively executing each function from `array` * over the result of a previous function call. * * All functions to execute must be unary (must take only one argument) and * must return single output value. * * `initial` value will be resolved using `Promise.resolve` method. * Functions to call will be passed to `Promise.then` method. * * NB. there is no way to handle errors within promises using this function. * * Delegates to `reduce` method of `arr` argument. * * @deprecated consider using `pipe` with `then` function * (for better error handling) * * @function * @category Array * * @param {Array} arr array of functions * @returns {Function} a new function to take `initial` value as an argument * and execute one by one function from source `array` producing a single * output value (an instance of Promise, ie. `thenable`) * * @example * import {pipeP} from '@yagni-js/yagni'; * * function add2(x) { return x + 2; } * function square(x) { return x * x; } * function minus7(x) { return x - 7; } * * const ops = pipeP([ * add2, * square, * minus7 * ]); * * const res = ops(5); // => resolved Promise with 42 as a result value * */ function pipeP(arr) { return function _pipeP(initial) { return arr.reduce(function (acc, piper) { return acc.then(piper); }, Promise.resolve(initial)); }; } /** * Takes a function `predicate` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and returns **a new array**, * containing items from `arr` which satisfy `predicate`. * * Delegates to `filter` method of `arr` argument. * * @category Array * * @param {Function} predicate function to check if an array item satisfies * the condition * @returns {Function} a new function to take an array `array` as an argument * and produce a new array containing items which satisfy `predicate` * * @example * import {filter} from '@yagni-js/yagni'; * * function belowZero(x) { return x < 0; } * const negatives = filter(belowZero); * * const values = negatives([-1, 2, 4, 3, -3, -2, 0]); // => [-1, -3, -2] * */ function filter(predicate) { return function _filter(arr) { return arr.filter(predicate); }; } /** * Takes a function `predicate` and function `mapper` as arguments * and returns **a new function**, * which then takes an array `arr` as an argument and returns **a new array**, * produced by applying `mapper` to such elements of `arr`, which satisfy * `predicate`. * * Delegates to `reduce` method of `arr` argument. * * @category Array * * @param {Function} predicate function to check if an array item satisfies * the condition * @param {Function} mapper function to apply to satisfied elements of array * @returns {Function} a new function to take an array `arr` as an argument * and produce a new array by applying `mapper` to such elements of `arr`, * which satisfy `predicate` * * @example * import {filterMap} from '@yagni-js/yagni'; * * function aboveZero(x) { return x > 0; } * function square(x) { return x * x; } * * const doublePositives = filterMap(aboveZero, square); * * const values = doublePositives([-1, 1, -2, 2, -3, 3]); // => [1, 4, 9] * */ function filterMap(predicate, mapper) { return reduceToArr( function _filterMap(acc, smth) { return predicate(smth) ? acc.concat(mapper(smth)) : acc; } ); } /** * Takes an array `arr` as an argument and returns **a new function**, * which then takes some value `smth` as an argument and returns * **a new array**, produced by concatenating `arr` and `smth`. * * Delegates to `concat` method of `arr` argument. * * @category Array * * @param {Array} arr source array * @returns {Function} a new function to take some value `smth` as an argument * and produce a new array by concatenating source `arr` with `smth` * * @example * import {concat} from '@yagni-js/yagni'; * * const a = ['foo']; * const b = ['baz']; * * const c = concat(a); * * const d = c(b); // => ['foo', 'baz'] * */ function concat(arr) { return function _concat(smth) { return arr.concat(smth); }; } /** * Takes `separator` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and returns **string**, * produced by concatenating all of the elements in `arr`, * separated by specified `separator`. * * If `separator` is not defined (`undefined` or `null`) `arr` will be * concatenated using *comma* as a separator. * * Delegates to `join` method of `arr` argument. * * @category Array * * @param {*} separator string to separate adjacent elements of array * @returns {Function} a new function to take array `arr` as an argument * and produce a string by concatenating all of the elements in `arr` * separated by `separator` * * @example * import {join} from '@yagni-js/yagni'; * * const commaJoin = join(); * const dotJoin = join('.'); * * const data = ['foo', 'baz', 'bar']; * * const res0 = commaJoin(data); // => 'foo,baz,bar' * const res1 = dotJoin(data); // => 'foo.baz.bar' * */ function join(separator) { return function _join(arr) { return isDefined(separator) ? arr.join(separator) : arr.join(); }; } /** * Takes an array `arr` and produces **a new array** by taking all elements * from source `arr` and it's sub-arrays and placing them in a new array. * * Uses `concat` method of new array. * * @function * @category Array * * @param {Array} arr source array to flatten * @returns {Array} flattened version of source array * * @example * import {flatten} from '@yagni-js/yagni'; * * const src = [[1, 2], [[3], 4, [[5]]]]; * * const dst = flatten(src); // => [1, 2, 3, 4, 5] * */ const flatten = reduceToArr( function _flatten(acc, item) { return acc.concat(isArray(item) ? flatten(item) : item); } ); /** * Takes a function `mapper` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and produces **a new array** * by taking all elements from source `arr` and it's sub-arrays, * applying `mapper` to them and placing the result in a new array. * * Uses `reduce` method of source array and `concat` method of new array. * * @category Array * * @param {Function} mapper function to apply to each element of new array * @returns {Function} a new function to take an array `arr` as an argument * and produce a new array by taking all elements from source `arr` and * it's sub-arrays, apply `mapper` to them and place the result in a new array * * @see flatten * * @example * import {flattenMap} from '@yagni-js/yagni'; * * function square(x) { return x * x; } * * const processor = flattenMap(square); * * const src = [[1, 2], [[3], 4, [[5]]]]; * * const dst = processor(src); // => [1, 4, 9, 16, 25] * */ function flattenMap(mapper) { return function _flattenMap(arr) { return arr.reduce( function __flattenMap(acc, item) { return acc.concat(isArray(item) ? _flattenMap(item) : mapper(item)); }, [] ); }; } /** * Takes a function `predicate` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and returns **true** if * all `arr` items satisfy `predicate` or **false** otherwise. * * Delegates to `every` method of `arr` argument. * * @category Array * * @param {Function} predicate function to check if an array item satisfies * the condition * @returns {Function} a new function to take an array `arr` as an argument * and return `true` if all `arr` items satisfy `predicate` or `false` otherwise * * @example * import {all} from '@yagni-js/yagni'; * * function below10(x) { return x < 10; } * const allBelow10 = all(below10); * * const res0 = allBelow10([2, 4, 6]); // => true * const res1 = allBelow10([12, 4, 6]); // => false * */ function all(predicate) { return function _all(arr) { return arr.every(predicate); }; } /** * Takes a function `predicate` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and returns **true** if * at least one `arr` item satisfy `predicate` or **false** otherwise. * * Delegates to `some` method of `arr` argument. * * @category Array * * @param {Function} predicate function to check if an array item satisfies * the condition * @returns {Function} a new function to take an array `arr` as an argument * and return `true` if at least one `arr` item satisfy `predicate` * or `false` otherwise * * @example * import {any} from '@yagni-js/yagni'; * * function below10(x) { return x < 10; } * const anyBelow10 = any(below10); * * const res0 = anyBelow10([10, 20, 30, 5, 40]); // => true * const res1 = anyBelow10([10, 20, 30, 40, 50]); // => false * */ function any(predicate) { return function _any(arr) { return arr.some(predicate); }; } /** * Takes `smth` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and returns * **the first index** at which `smth` can be found in the `arr` or -1 if * it is not present. * * Delegates to `indexOf` method of `arr` argument. * * @category Array * * @param {*} smth element to locate in array * @returns {Function} a new function to take array `arr` as an argument * and return the first index at which `smth` can be found in `arr` or -1 if * it is not present in `arr` * * @example * import {index} from '@yagni-js/yagni'; * * const data = ['foo', 'baz', 'bar']; * * const fooIndex = index('foo'); * const ftIndex = index(42); * * const res0 = fooIndex(data); // => 0 * const res1 = ftIndex(data); // => -1 * */ function index(smth) { return function _index(arr) { return arr.indexOf(smth); }; } /** * Takes an array `arr` as an argument and returns **a new function**, * which then takes `smth` as an argument and returns * **the first index** at which `smth` can be found in the `arr` or -1 if * it is not present. * * Delegates to `indexOf` method of `arr` argument. * * @category Array * * @param {Array} arr array to search for some element's index in * @returns {Function} a new function to take `smth` as an argument * and return the first index at which `smth` can be found in `arr` or -1 if * it is not present in `arr` * * @example * import {indexIn} from '@yagni-js/yagni'; * * const data = ['foo', 'baz', 'bar']; * const getIndexIn = indexIn(data); * * const res0 = getIndexIn('foo'); // => 0 * const res1 = getIndexIn(42); // => -1 * */ function indexIn(arr) { return function _indexIn(smth) { return arr.indexOf(smth); }; } /** * Takes `smth` as an argument and returns **a new function**, * which then takes an array `arr` as an argument and returns * **true** if `smth` is present in `arr` or **false** otherwise. * * Delegates to `indexOf` method of `arr` argument. * * @category Array * * @param {*} smth element to locate in array * @returns {Function} a new function to take array `arr` as an argument * and return true if `smth` was found in `arr` or false otherwise * * @example * import {exists} from '@yagni-js/yagni'; * * const ftExists = exists(42); * * const res0 = ftExists(['foo', 'baz', 'bar']); // => false * const res1 = ftExists(['foo', '42', 'bar']); // => true * */ function exists(smth) { return pipe([ index(smth), not(equals(-1)) ]); } /** * Takes an array `arr` as an argument and returns **a new function**, * which then takes `smth` as an argument and returns * **true** if `smth` is present in `arr` or **false** otherwise. * * Delegates to `indexOf` method of `arr` argument. * * @category Array * * @param {Array} arr array to check some element is inside * @returns {Function} a new function to take `smth` as an argument * and return true if `smth` was found in `arr` or false otherwise * * @example * import {existsIn} from '@yagni-js/yagni'; * * const isInside = existsIn(['foo', 'baz', 42]); * * const res0 = isInside('foo'); // => true * const res1 = isInside('bar'); // => false * const res2 = isInside(42); // => true * */ function existsIn(arr) { return pipe([ indexIn(arr), not(equals(-1)) ]); } /** * Takes an array `arr` and produces **a new array** by taking unique elements * from source `arr` and placing them in a new array. * * Uses `concat` and `indexOf` methods of new array. * * @function * @category Array * * @param {Array} arr source array to get unique elements from * @returns {Array} array with unique elements from `arr` * * @example * import {unique} from '@yagni-js/yagni'; * * const src = [1, 2, 2, 3, 2, 3, 4, 4, 5]; * * const dst = unique(src); // => [1, 2, 3, 4, 5] * */ const unique = reduceToArr( function _unique(acc, item) { return acc.indexOf(item) === -1 ? acc.concat(item) : acc; } ); /** * Takes some value `smth` as an argument and returns it back. * * @category Function * * @param {*} smth some value * @returns {*} `smth` * * @example * * import {identity} from '@yagni-js/yagni'; * * const foo = identity('foo'); // => 'foo' * */ function identity(smth) { return smth; } /** * Takes some value `smth` as an argument and returns **a new function**, * which when called always return `smth`. * * @category Function * * @param {*} smth some value * @returns {Function} a new function which when called always return `smth` * * @example * * import {always} from '@yagni-js/yagni'; * * const answer = _.always(42); * * const res0 = answer(); // => 42 * const res1 = answer('foo'); // => 42 * const res2 = answer('baz'); // => 42 * const res3 = answer(true); // => 42 * const res4 = answer([12]); // => 42 * */ function always(smth) { return function _always() { return smth; }; } /** * Lazily evaluates function result. * * Takes an unary function `caller` and some value `arg` for it and returns * **a new function**, which when called always returns the result of * evaluating `caller(arg)`. * * @category Function * * @param {Function} caller function to call * @param {*} arg some value as an argument for `caller` * @returns {Function} a new function to call and return the result of * `caller(arg)` * * @example * * import {lazy} from '@yagni-js/yagni'; * * function add2(x) { return x + 2; } * * const fixedPrice = lazy(add2, 40); * * const res0 = fixedPrice(); // => 42 * const res1 = fixedPrice('foo'); // => 42 * */ function lazy(caller, arg) { return function _lazy() { return caller(arg); }; } /** * Takes a function or some value `smth` as a first argument and * some value `arg` as a second argument and returns result of calling * `smth(arg)` if `smth` is a function; or just `smth` if it's not a function * * @category Function * * @param {Function|*} smth function or some value * @param {*} arg argument for `smth` if it's a function * @returns {*} result of calling `smth(arg)` if `smth` is a function; * `smth` otherwise * * @see resultArr * * @example * * import {result} from '@yagni-js/yagni'; * * function add2(x) { return x + 2; } * * const res0 = result(42); // => 42 * const res1 = result('foo', 42); // => 'foo' * const res2 = result(add2, 40); // => 42 */ function result(smth, arg) { return isFunction(smth) ? smth(arg) : smth; } /** * Takes an array `arr` of functions or some values as a first argument * and some value `arg` as a second argument and returns **a new array**, * produced by calling `result(item, arg)` over each element of `arr`. * * Uses `map` method of `arr` argument. * * @category Function * * @param {Array} arr array of functions or some values * @returns {Array} a new array produced by calling `result(item, arg)` * over each element of `arr` * * @see result * * @example * * import {resultArr} from '@yagni-js/yagni'; * * const src = [ * 'foo', * function single(x) { return x; }, * function double(x) { return x * x; }, * function triple(x) { return x * x * x; }, * 42]; * * const res = resultArr(src, 2); // => ['foo', 2, 4, 8, 42] * */ function resultArr(arr, arg) { return arr.map(function _resultArr(smth) { return result(smth, arg); }); } /** * Takes two functions `caller` and `argGetter` as arguments and returns * **a new function** which then takes some value `smth` as an argument * and returns result of calling `caller(argGetter(smth))`. * * @category Function * * @param {Function} caller target function to call * @param {Function} argGetter function to call to get an argument `arg` * @returns {Function} a new function to take some value `smth`, call * `argGetter(smth)` to get some value `arg` and return the result of calling * `caller(arg)` * * @example * * import {fn, pick} from '@yagni-js/yagni'; * * function add2(x) { return x + 2; } * const pickFoo = pick('foo'); * * const answer = fn(add2, pickFoo); * * const src = {foo: 40}; * * const res = answer(src); // => 42 * */ function fn(caller, argGetter) { return function _fn(smth) { const param = argGetter(smth); return caller(param); }; } /** * Takes three functions `caller`, `argGetter1` and `argGetter2` * as arguments and returns * **a new function** which then takes some value `smth` as an argument * and returns result of calling `caller(argGetter1(smth), argGetter2(smth))`. * * @category Function * * @param {Function} caller target function to call * @param {Function} argGetter1 function to call to get an argument `arg1` * @param {Function} argGetter2 function to call to get an argument `arg2` * @returns {Function} a new function to take some value `smth`, call * `argGetter1(smth)` and `argGetter2(smth)` to get some values * `arg1` and `arg2` and return the result of calling `caller(arg1, arg2)` * * @example * * import {fn2, obj, pick} from '@yagni-js/yagni'; * * const pickFoo = pick('foo'); * const pickBaz = pick('baz'); * * const toObj = fn2(obj, pickFoo, pickBaz); * * const src = {foo: 'key', baz: 'value'}; * * const res = toObj(src); // => {key: 'value'} * */ function fn2(caller, arg1Getter, arg2Getter) { return function _fn2(smth) { const arg1 = arg1Getter(smth); const arg2 = arg2Getter(smth); return caller(arg1, arg2); }; } /** * Takes some object `subj` as a first argument and `methodName` as a second * argument and returns **a new function**, which then takes some value `smth` * as an argument and returns result of calling `methodName` method of `subj` * using `smth` as an argument. * * @category Function * * @param {Object} subj owner of a method to call * @param {String} methodName method name * @returns {Function} a new function to take some value `smth` and return * the result of calling `methodName` method of `subj` using `smth` as an * argument * * @example * * import {method} from '@yagni-js/yagni'; * * const stringify = method(JSON, 'stringify'); * * const src = {foo: 42, 'baz': true}; * * const res = stringify(src); // => '{"foo":42,"baz":true}' * */ function method(subj, methodName) { return function _method(smth) { return subj[methodName](smth); }; } /** * Takes two functions `fnGetter` and `argOrGetter` as arguments and returns * **a new function** which then takes some value `smth` as an argument * and: * - calls `fnGetter(smth)` to get target function `caller` to call * - calls `argOrGetter(smth)` to get an argument `arg` to use for `caller` * - returns the result of `caller(arg)` call. * * `argOrGetter` can be a function or some static value to use as an `arg` * argument for `caller`. * * @category Function * * @param {Function} fnGetter function to call to ret