API Docs for:
Show:

File: development/js/utils/func.js

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
}