@yagni-js/yagni
Version:
Yet another functional library
1,871 lines (1,759 loc) • 75.3 kB
JavaScript
/**
*
* @module @yagni-js/yagni
* @version 0.7.1
* @author Yuri Egorov <ysegorov at gmail dot com>
* @license Unlicense http://unlicense.org
*
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**
* 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`.
*