Home Reference Source Test

src/Function/index.js

import * as ArrLib from '../Array';
import { isNotFunction } from '../Type';

/**
 * @desc Creates a function that accepts arguments of func and either
 * invokes func returning its result, if at least arity number
 * of arguments have been provided, or returns a function that
 * accepts the remaining func arguments, and so on. The arity
 * of func may be specified if func.length is not sufficient.
 * @param {Function} fn Function for Curring.
 * @return {Function|T} Function that should collect arguments.
 */
export function curry(fn) {
  return function curriedFn(...args) {
    if(args.length < fn.length) {
      return function() {
        return curriedFn.call(null, ...args, ...Array.from(arguments)); };
      };
    return fn.apply(null, args);
  };
}

/**
 * @desc Creates a function that invokes fn with partials prepended
 * to the arguments it receives.
 * @param {Function} fn The function to partially apply arguments to.
 * @param {...T} partialArgs Arguments that should be applied.
 * @return {Function} Returns the new partially applied function.
 */
export function partial(fn, ...partialArgs) {
  let fnArgsLength = fn.length
    , fnArgs = partialArgs
    , canInvokeFn = () =>
      fnArgs.length >= fnArgsLength && ArrLib.compact(fnArgs).length >= fnArgsLength
    , getArgs = (...newArgs) => {
      fnArgs = ArrLib.times(fnArgsLength, index => fnArgs[index] || newArgs.pop());
      return (canInvokeFn()) ? fn(...fnArgs) : getArgs;
    }

  return getArgs;
}

/**
 * @desc This method is like pipe except that it creates a function that
 * invokes the provided functions from right to left.
 * @param {...Function} fns Function that should be invoked with given arguments.
 * @return {Function} Returns the new function.
 */
export function compose(...fns) {
  return val =>
    fns.reverse().reduce((accumulator, fn) =>
      fn(accumulator), val);
}

/**
 * @desc Creates a function that returns the result of invoking the provided f
 * unctions with the this binding of the created function, where each successive
 * invocation is supplied the return value of the previous.
 * @param {...Function} fns Function that should be invoked with given arguments.
 * @return {Function} Returns the new function.
 */
export function pipe(...fns) {
  return (...vals) =>
    fns.reduce((accumulator, fn) =>
      fn(accumulator), fns.shift()(...vals));
}

/**
 * @desc Creates a function that memoizes the result of fn.
 * If resolver is provided, it determines the cache key for storing
 * the result based on the arguments provided to the memoized function.
 * By default, the first argument provided to the memoized function is
 * used as the map cache key.
 * @param {Function} fn Function to memoize.
 * @return {Function} Memoized function.
 */
export function memoize(fn) {
  let lookupTable = {};
  return arg => (lookupTable[arg] ||
    (lookupTable[arg] = fn(arg)));
};

/**
 * @desc Creates a function that is restricted to invoking func once.
 * Repeat calls to the function return undefined.
 * @param {Function} fn Function that should be invoked only once.
 * @return {T|undefined} Returns invocation result of passed function.
 */
export function once(fn) {
  let isDone = false;
  return () => isDone ? undefined : ((isDone = true), fn.apply(this, arguments));
};

/**
 * @desc Create funtion that makes same as passed function but newly created
 * function will use only first argument.
 * @param {Function} fn Function that should use only one argument.
 * @return {Function} Same function that was passed but now it uses only
 * first argument.
 */
export function unary(fn) {
  return fn.length === 1
    ? fn : (arg) => fn(arg);
};

/**
 * @desc Reorginizes argument order.
 * @param {Function} fn Function that should be invoked with
 * reorganized argument order.
 * @param {Array<number>} order Argument order.
 */
export function rearg(fn, order) {
  return (...values) =>
    fn(...ArrLib.changeOrder(values, order));
};

/**
 * Creates a function that invokes fn once it's called n or more times.
 * @param {number} count The number of calls before func is invoked.
 * @param {Function} fn The function to restrict.
 * @returns {Function} Function that invokes fn once it's called n or more times.
 */
export function after(count, fn){
  let runCount = 0;
  return function() {
    runCount = runCount + 1;
    if (runCount >= count) {
      return fn.apply(this, arguments);
    }
  }
}

/**
 * @desc Creates curry variation of function if some of
 * arguments are missed.
 * @param {Function} fn Function for Curring.
 * @param {ArrayLike|Array<any>} args Arguments which should be invoked woith passed function.
 * @return {Function|T} Function that should collect arguments or result on invokation.
 */
export function orCurry(fn, args) {
  if(isNotFunction(fn)) return orCurry;

  let argsWithoutUndefined = Array.from(args)
    .filter(i => i !== undefined);

  return fn.length === argsWithoutUndefined.length
    ? fn(...argsWithoutUndefined)
    : curry(fn)(...argsWithoutUndefined);
}