import type from './type';
import arrays from './array';
/**
* Provides a whole bunch of utility functions
*
* @module Utils
* @class Utils.Funcs
* @static
*/
/**
* Takes a function with a arity of N and transforms it into a arity of zero
*
* @method nullary
* @for Utils.Funcs
* @param {function} fn The function to decorate
* @return {function} The decorated function
*/
export const nullary = function (fn) {
return function () {
return fn.call(this);
}
}
/**
* Takes a function with a arity of N and transforms it into a arity of one
*
* @method unary
* @for Utils.Funcs
* @param {function} fn The function to decorate
* @return {function} The decorated function
*
* @example
* import {unary} from './utils/func';
*
* var mapInt = function (nums) {
* return nums.map(parseInt);
* }
*
* mapInt(['1', '2', '3', '4', '5']);
* // -> [1, NaN, NaN, NaN, NaN]
*
*
*
* var unaryMapInt = function (nums) {
* return nums.map(unary(parseInt));
* }
*
* unaryMapInt(['1', '2', '3', '4', '5']);
* // -> [1, 2, 3, 4, 5]
*/
export const unary = function (fn) {
return function (arg) {
if (arg === void 0) {
return unary(fn);
}
return fn.call(this, arg);
}
}
/**
* Takes a function with a arity of N and transforms it into a arity of 2
*
* @method binary
* @for Utils.Funcs
* @param {function} fn The function to decorate
* @return {function} The decorated function
*
* @example
* import {binary} from './utils/func';
*
* var say = function (sentence, to) {
* return sentence + ' ' + to;
* }
*
* say('Hi', 'Bob');
* // -> 'Hi Bob'
*
*
* var sayHi = binary(say)('Hi');
*
* sayHi('Bob');
* // -> 'Hi Bob'
*/
export const binary = function (fn) {
return function (arg1, arg2) {
if (arg1 === void 0) {
return binary(fn);
}
if (arg2 === void 0) {
return unary(function (_arg2) {
return fn.call(this, arg1, _arg2);
});
}
return fn.call(this, arg1, arg2);
}
}
/**
* Takes a function with a arity of N and transforms it into a arity of 3
*
* @method ternary
* @for Utils.Funcs
* @param {function} fn The function to decorate
* @return {function} The decorated function
*
* @example
* import {ternary} from './utils/func';
*
* var say = function (greet, sentence, to) {
* var said = greet || '';
* if (sentence) {
* said += ' ' + sentence;
* }
* if (to) {
* said += ' ' + to;
* }
* return said;
* }
*
* say('Hi', 'user', 'Bob');
* // -> 'Hi user Bob'
*
*
* var sayHi = ternary(say)('Hi');
*
* sayHi('user', 'Bob');
* // -> 'Hi user bob'
*/
export const ternary = function (fn) {
return function (arg1, arg2, arg3) {
if (arg1 === void 0) {
return ternary(fn);
}
if (arg2 === void 0) {
return binary(function (_arg2, _arg3) {
return fn.call(this, arg1, _arg2, _arg3);
});
}
if (arg3 === void 0) {
return unary(function (_arg3) {
return fn.call(this, arg1, arg2, _arg3);
});
}
return fn.call(this, arg1, arg2, arg3);
}
}
/**
* Takes a function with a arity of N and transforms it into a arity of 4
*
* @method quaternary
* @for Utils.Funcs
* @param {function} fn The function to decorate
* @return {function} The decorated function
*
* @example
* SEE Utils.binary EXAMPLE
*/
export const quaternary = function (fn) {
return function (arg1, arg2, arg3, arg4) {
if (arg1 === void 0) {
return quaternary(fn);
}
if (arg2 === void 0) {
return ternary(function (_arg2, _arg3, _arg4) {
return fn.call(this, arg1, _arg2, _arg3, _arg4);
});
}
if (arg3 === void 0) {
return binary(function (_arg3, _arg4) {
return fn.call(this, arg1, arg2, _arg3, _arg4);
});
}
if (arg4 === void 0) {
return unary(function (_arg4) {
return fn.call(this, arg1, arg2, arg3, _arg4);
});
}
return fn.call(this, arg1, arg2, arg3, arg4);
}
}
/**
* Takes a function with a arity of N and transforms it into a variadic
* version, which takes a certain number of arguments (same as original)
* but collects the last and all following arguments into a array as
* last argument
*
* @method multary
* @deprecated Unneeded since ES6/ES2015, will be removed in future releases
* @for Utils.Funcs
* @param {function} fn The function to decorate
* @return {function} The decorated function
*
* @example
*
*/
export const multary = function (fn) {
var _arity;
if (!type.isFunction(fn) || fn.length < 1) {
return fn;
}
_arity = fn.length;
return function accumulator () {
var _argsGiven = arguments.length > 1 ? arrays.take(arguments, _arity - 1) : [],
_argsMiss = new Array(Math.max(_arity - arguments.length - 1, 0)),
_argsRest = arrays.drop(arguments, _arity - 1);
return fn.apply(this, _argsGiven.concat(_argsMiss).concat([_argsRest]));
}
}
/**
* Takes a function, a context and optional partial arguments and returns a
* function which is always executed in the given context
*
* @method bind
* @for Utils.Funcs
* @deprecated Use native Function.prototype.bind instead
* @param {function} fn The function to decorate
* @param {object} scope The context of the function
* @param {*} [partials] Partial arguments to preset
* @return {function} The bound function
*
* @example
* var person = {
* name: 'John Doe',
* sayHi: function () {
* return 'Hi, my name is ' + this.name;
* }
* }
*
* person.sayHi();
* // -> 'Hi, my name is John Doe'
*
* var personSaysHi = person.sayHi;
*
* personSaysHi();
* // -> Gobbledygokk
*
* var boundSaysHi = Utils.bind(person.sayHi, person);
*
* boundSaysHi();
* // -> 'Hi, my name is John Doe'
*/
export const bind = function (fn, scope, partials) {
return fn.bind(scope, partials)
}
/**
* Takes a function and returns a function which calls the given function
* in the current context and returns that context. This is useful to
* define fluent interfaces
*
* @method fluent
* @for Utils.Funcs
* @param {function} fn The function to decorate
* @return {function} The fluent function
*
* @example
* import {fluent} from './utils/func';
*
* var person = {
* name: 'John Doe',
* formatName: function () {
* this.name = this.name.toUpperCase();
* }
* }
*
* person.formatName().name;
* // -> Error, cannot read property name of undefined
*
*
*
* var fluentPerson = {
* name: 'John Doe',
* formatName: fluent(function () {
* this.name = this.name.toUpperCase();
* })
* }
*
* fluentPerson.formatName().name;
* // -> 'JOHN DOE'
*/
export const fluent = function (fn) {
return function () {
fn.apply(this, arguments);
return this;
}
}
/**
* Takes one to N functions and returns a single function which executes all
* contained function in order. Please note that the given functions are
* executed from right-to-left. For the opposite behaviour just use the
* Utils.pipe function
*
* @method compose
* @for Utils.Funcs
* @param {function} [fns*] One to N functions to compose together
* @return {function} Composition of functions
*
* @example
* import {compose} from './utils/func';
*
* var joinWithSpace = function (strArray) {
* return strArray.join(' ');
* }
*
* var splitAtSpaces = function (str) {
* return str.split(' ')
* }
*
* var firstUpperAll = function (strArray) {
* return strArray.forEach(function (str) {
* return str[0].toUpperCase() + str.slice(1);
* });
* }
*
*
*
* var formatName = compose(
* joinWithSpace,
* firstUpperAll,
* splitAtSpaces
* );
*
* formatName('john doe');
* // -> 'John Doe'
*/
export const compose = function (...fns) {
return function (...args) {
var _data = args,
_i = fns.length - 1;
while (_i > -1) {
_data = [fns[_i].apply(this, _data)];
_i -= 1;
}
return _data[0];
}
}
/**
* Takes one to N functions and returns a single function which executes all
* contained function in order. Please note that the given functions are
* executed from left-to-right. For the opposite behaviour just use the
* Utils.compose function
*
* @method pipe
* @for Utils.Funcs
* @param {function} [fns*] One to N functions to pipe together
* @return {function} Pipeline of functions
*
* @example
* import {pipe} from './utils/func';
*
* var joinWithSpace = function (strArray) {
* return strArray.join(' ');
* }
*
* var firstUpperAll = function (strArray) {
* return strArray.forEach(function (str) {
* return str[0].toUpperCase() + str.slice(1);
* });
* }
*
* var splitAtSpaces = function (str) {
* return str.split(' ')
* }
*
*
*
* var formatName = pipe(
* splitAtSpaces,
* firstUpperAll,
* joinWithSpace
* );
*
* formatName('john doe');
* // -> 'John Doe'
*/
export const pipe = function (...fns) {
return function (...args) {
var _data = args,
_i = 0;
while (_i < fns.length) {
_data = [fns[_i].apply(this, _data)];
_i += 1;
}
return _data[0];
}
}
/**
* Takes a function and returns a function which itself returns the opposite
* boolean result of the original function
*
* @method negate
* @for Utils.Funcs
* @deprecated Use Utils.not instead
* @param {function} fn The function to negate
* @return {function} The negated function
*
* @example
* import {not, negate} from './utils/func';
*
* var truthy = function (x) {
* return !!x;
* }
*
* var falsy = not(truthy); // or falsy = negate(truthy)
*
*
* truthy(1);
* // -> true
*
* falsy(1);
* // -> false
*/
export const not = function (fn) {
return function (...args) {
return !fn.apply(this, args);
}
}
/**
* Takes a function and returns a function which itself throttles the execution
* of the given function for the given amount of milliseconds after the
* last invocation of the returned function. This is super useful if you
* add continuous firing events (like resize) but want your event listener
* to only fire N milliseconds after the last invocation of the event
*
* @method throttle
* @for Utils.Funcs
* @param {number} ms Milliseconds of throttling
* @param {function} fn The function to throttle
* @return {function} The throttled function
*
* @example
* import {throttle} from './utils/func';
*
* var onResize = throttle(250, function (event) {
* // do something with the event
* });
*
* $(document.body).on('resize', onResize);
*/
export const throttle = function (ms, fn) {
var _timer;
return function (...args) {
var _self = this;
if (_timer) {
clearTimeout(_timer);
}
_timer = setTimeout(() => { fn.apply(_self, args); }, ms);
}
}
/**
* Takes a function with a arity of two or more and returns a function, which
* takes the arguments one by one with subsequent invocations
*
* @method curry
* @for Utils.Funcs
* @param {function} fn The function to curry
* @return {function} The curried function
*
* @example
* import {curry} from './utils/func';
*
* var argsAsArray = function (a, b, c) {
* return [a, b, c];
* }
*
* var argsAsArrayCurried = curry(argsAsArray);
*
*
* argsAsArray(1);
* // -> [1, undefined, undefined]
*
* argsAsArrayCurried(1);
* // -> function
*
* argsAsArray(1)(2)(3);
* // -> [1, 2, 3]
*/
export const curry = function (fn) {
if (fn.length < 1) {
return fn;
}
if (fn.length < 2) {
return unary(fn);
}
function accumulator (partials, missing) {
return unary(function (nextArg) {
var _args = partials.concat(nextArg),
_miss = missing - _args.length;
if (_miss > 0) {
return accumulator(_args, _miss);
}
return fn.apply(this, _args);
});
}
return accumulator([], fn.length);
}
var fillPartialGaps = function (partials, argList) {
var filled = [],
l = partials.length,
i = 0;
while (i < l) {
filled[i] = partials[i] === undefined ? argList.shift() : partials[i];
i += 1;
}
return filled;
}
/**
* Takes a function and optional arguments and returns a partially applied
* function (meaning the already given arguments are predefined). Further
* calls return a new function until all needed arguments are given to
* execute the original function
*
* @method partial
* @for Utils.Funcs
* @param {function} fn The function to apply partially
* @param {*} [partials] Zero to N arguments to predefine
* @return {function} A partially applied function
*
* @example
* import {partial} from './utils/func';
*
* var argsAsArray = function (a, b, c) {
* return [a, b, c];
* }
*
* var argsAsArrayPartial = partial(argsAsArray, undefined, 2, 3);
*
*
* argsAsArray(1, 2, 3);
* // -> [1, 2, 3]
*
* argsAsArrayPartial(1, 5, 9);
* // -> [1, 2, 3]
*/
export const partial = function (fn) {
var _partials,
_argsGaps;
if (fn.length < 1) {
return fn;
}
_partials = arrays.rest(arguments);
_argsGaps = new Array(Math.max(fn.length - _partials.length - 1, 0));
function accumulator (partials) {
return function () {
var _args = fillPartialGaps(partials, arrays.toArray(arguments));
if (_args.indexOf(void 0) > -1) {
return accumulator(_args);
}
return fn.apply(this, _args);
}
}
return accumulator(_partials.concat(_argsGaps));
}
/**
* Takes a function and returns a memoized version of it which immediatly
* returns a already calculated result if the incoming arguments have
* been used on any preceeding invocation
*
* @method memoize
* @for Utils.Funcs
* @param {function} fn The function to memoize
* @return {function} A memoized function
*
* @example
* import {memoize} from './utils/func';
*
* var countInvocation = 0;
*
* var doComplicatedStuff = function (initArg) {
* countInvocation += 1;
* // calculates complex stuff and returns it
* }
*
* var memoDoComplicatedStuff = memoize(doComplicatedStuff);
*
*
*
* doComplicatedStuff('abc');
* doComplicatedStuff('abc');
* doComplicatedStuff('abc');
*
* countInvocation;
* // -> 3
*
* countInvocation = 0;
*
* memoDoComplicatedStuff('abc');
* memoDoComplicatedStuff('abc');
* memoDoComplicatedStuff('abc');
*
* countInvocation;
* // -> 1
*/
export const memoize = function (fn) {
var cached = {};
return function (...args) {
var _args = JSON.stringify(args);
if (!cached.hasOwnProperty(_args)) {
cached[_args] = fn.apply(this, args);
}
return cached[_args];
};
}
/**
* Takes a predicate/guarding function and a normal function and returns a
* function, which tests incoming arguments with the predicate function
* and only executes the normal function, if the predicate returns true
*
* @method given
* @for Utils.Funcs
* @param {function} predicate Predicate/guarding function
* @param {function} fn The normal function
* @return {function} A guarded function
*
* @example
* import {given} from './utils/func';
* import {isNumber} from './utils/type';
*
* var squareNum = given(isNumber, (n) => n * n);
*
* squareNum(2);
* // -> 4
*
* squareNum('abc');
* // -> null
*/
export const given = function (predicate, fn) {
return function () {
if (predicate.apply(this, arguments)) {
return fn.apply(this, arguments);
}
return null;
}
}
/**
* Takes a function and returns a function which only executes if none of
* the incoming arguments is null or undefined. Otherwise returns null
*
* @method maybe
* @for Utils.Funcs
* @param {function} fn The function to maybe execute
* @return {function} A function which is maybe executed
*
* @example
* import {maybe} from './utils/func';
*
* var maybeUpperCase = maybe((str) => str.toUpperCase());
*
* maybeUpperCase(null);
* // -> null, no Error!
*
* maybeUpperCase('abc');
* // -> 'ABC'
*/
export const maybe = function (fn) {
return function (...args) {
if (args.some(type.isNull)) {
return null;
}
return fn.apply(this, args);
}
}
export default {
nullary, unary, binary, ternary, quaternary, multary,
bind, curry, compose, pipe, partial, given, not,
maybe, memoize, throttle, fluent
}