michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Disclaimer: Some of the functions in this module implement APIs from michael@0: // Jeremy Ashkenas's http://underscorejs.org/ library and all credits for michael@0: // those goes to him. michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "unstable" michael@0: }; michael@0: michael@0: const { deprecateFunction } = require("../util/deprecate"); michael@0: const { setImmediate, setTimeout, clearTimeout } = require("../timers"); michael@0: michael@0: const arity = f => f.arity || f.length; michael@0: michael@0: const name = f => f.displayName || f.name; michael@0: michael@0: const derive = (f, source) => { michael@0: f.displayName = name(source); michael@0: f.arity = arity(source); michael@0: return f; michael@0: }; michael@0: michael@0: /** michael@0: * Takes variadic numeber of functions and returns composed one. michael@0: * Returned function pushes `this` pseudo-variable to the head michael@0: * of the passed arguments and invokes all the functions from michael@0: * left to right passing same arguments to them. Composite function michael@0: * returns return value of the right most funciton. michael@0: */ michael@0: const method = (...lambdas) => { michael@0: return function method(...args) { michael@0: args.unshift(this); michael@0: return lambdas.reduce((_, lambda) => lambda.apply(this, args), michael@0: void(0)); michael@0: }; michael@0: }; michael@0: exports.method = method; michael@0: michael@0: /** michael@0: * Takes a function and returns a wrapped one instead, calling which will call michael@0: * original function in the next turn of event loop. This is basically utility michael@0: * to do `setImmediate(function() { ... })`, with a difference that returned michael@0: * function is reused, instead of creating a new one each time. This also allows michael@0: * to use this functions as event listeners. michael@0: */ michael@0: const defer = f => derive(function(...args) { michael@0: setImmediate(invoke, f, args, this); michael@0: }, f); michael@0: exports.defer = defer; michael@0: // Exporting `remit` alias as `defer` may conflict with promises. michael@0: exports.remit = defer; michael@0: michael@0: /** michael@0: * Invokes `callee` by passing `params` as an arguments and `self` as `this` michael@0: * pseudo-variable. Returns value that is returned by a callee. michael@0: * @param {Function} callee michael@0: * Function to invoke. michael@0: * @param {Array} params michael@0: * Arguments to invoke function with. michael@0: * @param {Object} self michael@0: * Object to be passed as a `this` pseudo variable. michael@0: */ michael@0: const invoke = (callee, params, self) => callee.apply(self, params); michael@0: exports.invoke = invoke; michael@0: michael@0: /** michael@0: * Takes a function and bind values to one or more arguments, returning a new michael@0: * function of smaller arity. michael@0: * michael@0: * @param {Function} fn michael@0: * The function to partial michael@0: * michael@0: * @returns The new function with binded values michael@0: */ michael@0: const partial = (f, ...curried) => { michael@0: if (typeof(f) !== "function") michael@0: throw new TypeError(String(f) + " is not a function"); michael@0: michael@0: let fn = derive(function(...args) { michael@0: return f.apply(this, curried.concat(args)); michael@0: }, f); michael@0: fn.arity = arity(f) - curried.length; michael@0: return fn; michael@0: }; michael@0: exports.partial = partial; michael@0: michael@0: /** michael@0: * Returns function with implicit currying, which will continue currying until michael@0: * expected number of argument is collected. Expected number of arguments is michael@0: * determined by `fn.length`. Using this with variadic functions is stupid, michael@0: * so don't do it. michael@0: * michael@0: * @examples michael@0: * michael@0: * var sum = curry(function(a, b) { michael@0: * return a + b michael@0: * }) michael@0: * console.log(sum(2, 2)) // 4 michael@0: * console.log(sum(2)(4)) // 6 michael@0: */ michael@0: const curry = new function() { michael@0: const currier = (fn, arity, params) => { michael@0: // Function either continues to curry arguments or executes function michael@0: // if desired arguments have being collected. michael@0: const curried = function(...input) { michael@0: // Prepend all curried arguments to the given arguments. michael@0: if (params) input.unshift.apply(input, params); michael@0: // If expected number of arguments has being collected invoke fn, michael@0: // othrewise return curried version Otherwise continue curried. michael@0: return (input.length >= arity) ? fn.apply(this, input) : michael@0: currier(fn, arity, input); michael@0: }; michael@0: curried.arity = arity - (params ? params.length : 0); michael@0: michael@0: return curried; michael@0: }; michael@0: michael@0: return fn => currier(fn, arity(fn)); michael@0: }; michael@0: exports.curry = curry; michael@0: michael@0: /** michael@0: * Returns the composition of a list of functions, where each function consumes michael@0: * the return value of the function that follows. In math terms, composing the michael@0: * functions `f()`, `g()`, and `h()` produces `f(g(h()))`. michael@0: * @example michael@0: * michael@0: * var greet = function(name) { return "hi: " + name; }; michael@0: * var exclaim = function(statement) { return statement + "!"; }; michael@0: * var welcome = compose(exclaim, greet); michael@0: * michael@0: * welcome('moe'); // => 'hi: moe!' michael@0: */ michael@0: function compose(...lambdas) { michael@0: return function composed(...args) { michael@0: let index = lambdas.length; michael@0: while (0 <= --index) michael@0: args = [lambdas[index].apply(this, args)]; michael@0: michael@0: return args[0]; michael@0: }; michael@0: } michael@0: exports.compose = compose; michael@0: michael@0: /* michael@0: * Returns the first function passed as an argument to the second, michael@0: * allowing you to adjust arguments, run code before and after, and michael@0: * conditionally execute the original function. michael@0: * @example michael@0: * michael@0: * var hello = function(name) { return "hello: " + name; }; michael@0: * hello = wrap(hello, function(f) { michael@0: * return "before, " + f("moe") + ", after"; michael@0: * }); michael@0: * michael@0: * hello(); // => 'before, hello: moe, after' michael@0: */ michael@0: const wrap = (f, wrapper) => derive(function wrapped(...args) { michael@0: return wrapper.apply(this, [f].concat(args)); michael@0: }, f); michael@0: exports.wrap = wrap; michael@0: michael@0: /** michael@0: * Returns the same value that is used as the argument. In math: f(x) = x michael@0: */ michael@0: const identity = value => value; michael@0: exports.identity = identity; michael@0: michael@0: /** michael@0: * Memoizes a given function by caching the computed result. Useful for michael@0: * speeding up slow-running computations. If passed an optional hashFunction, michael@0: * it will be used to compute the hash key for storing the result, based on michael@0: * the arguments to the original function. The default hashFunction just uses michael@0: * the first argument to the memoized function as the key. michael@0: */ michael@0: const memoize = (f, hasher) => { michael@0: let memo = Object.create(null); michael@0: let cache = new WeakMap(); michael@0: hasher = hasher || identity; michael@0: return derive(function memoizer(...args) { michael@0: const key = hasher.apply(this, args); michael@0: const type = typeof(key); michael@0: if (key && (type === "object" || type === "function")) { michael@0: if (!cache.has(key)) michael@0: cache.set(key, f.apply(this, args)); michael@0: return cache.get(key); michael@0: } michael@0: else { michael@0: if (!(key in memo)) michael@0: memo[key] = f.apply(this, args); michael@0: return memo[key]; michael@0: } michael@0: }, f); michael@0: }; michael@0: exports.memoize = memoize; michael@0: michael@0: /** michael@0: * Much like setTimeout, invokes function after wait milliseconds. If you pass michael@0: * the optional arguments, they will be forwarded on to the function when it is michael@0: * invoked. michael@0: */ michael@0: const delay = function delay(f, ms, ...args) { michael@0: setTimeout(() => f.apply(this, args), ms); michael@0: }; michael@0: exports.delay = delay; michael@0: michael@0: /** michael@0: * Creates a version of the function that can only be called one time. Repeated michael@0: * calls to the modified function will have no effect, returning the value from michael@0: * the original call. Useful for initialization functions, instead of having to michael@0: * set a boolean flag and then check it later. michael@0: */ michael@0: const once = f => { michael@0: let ran = false, cache; michael@0: return derive(function(...args) { michael@0: return ran ? cache : (ran = true, cache = f.apply(this, args)); michael@0: }, f); michael@0: }; michael@0: exports.once = once; michael@0: // export cache as once will may be conflicting with event once a lot. michael@0: exports.cache = once; michael@0: michael@0: // Takes a `f` function and returns a function that takes the same michael@0: // arguments as `f`, has the same effects, if any, and returns the michael@0: // opposite truth value. michael@0: const complement = f => derive(function(...args) { michael@0: return args.length < arity(f) ? complement(partial(f, ...args)) : michael@0: !f.apply(this, args); michael@0: }, f); michael@0: exports.complement = complement; michael@0: michael@0: // Constructs function that returns `x` no matter what is it michael@0: // invoked with. michael@0: const constant = x => _ => x; michael@0: exports.constant = constant; michael@0: michael@0: // Takes `p` predicate, `consequent` function and an optional michael@0: // `alternate` function and composes function that returns michael@0: // application of arguments over `consequent` if application over michael@0: // `p` is `true` otherwise returns application over `alternate`. michael@0: // If `alternate` is not a function returns `undefined`. michael@0: const when = (p, consequent, alternate) => { michael@0: if (typeof(alternate) !== "function" && alternate !== void(0)) michael@0: throw TypeError("alternate must be a function"); michael@0: if (typeof(consequent) !== "function") michael@0: throw TypeError("consequent must be a function"); michael@0: michael@0: return function(...args) { michael@0: return p.apply(this, args) ? michael@0: consequent.apply(this, args) : michael@0: alternate && alternate.apply(this, args); michael@0: }; michael@0: }; michael@0: exports.when = when; michael@0: michael@0: // Apply function that behaves as `apply` does in lisp: michael@0: // apply(f, x, [y, z]) => f.apply(f, [x, y, z]) michael@0: // apply(f, x) => f.apply(f, [x]) michael@0: const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop())); michael@0: exports.apply = apply; michael@0: michael@0: // Returns function identical to given `f` but with flipped order michael@0: // of arguments. michael@0: const flip = f => derive(function(...args) { michael@0: return f.apply(this, args.reverse()); michael@0: }, f); michael@0: exports.flip = flip; michael@0: michael@0: michael@0: // Takes field `name` and `target` and returns value of that field. michael@0: // If `target` is `null` or `undefined` it would be returned back michael@0: // instead of attempt to access it's field. Function is implicitly michael@0: // curried, this allows accessor function generation by calling it michael@0: // with only `name` argument. michael@0: const field = curry((name, target) => michael@0: // Note: Permisive `==` is intentional. michael@0: target == null ? target : target[name]); michael@0: exports.field = field; michael@0: michael@0: // Takes `.` delimited string representing `path` to a nested field michael@0: // and a `target` to get it from. For convinience function is michael@0: // implicitly curried, there for accessors can be created by invoking michael@0: // it with just a `path` argument. michael@0: const query = curry((path, target) => { michael@0: const names = path.split("."); michael@0: const count = names.length; michael@0: let index = 0; michael@0: let result = target; michael@0: // Note: Permisive `!=` is intentional. michael@0: while (result != null && index < count) { michael@0: result = result[names[index]]; michael@0: index = index + 1; michael@0: } michael@0: return result; michael@0: }); michael@0: exports.query = query; michael@0: michael@0: // Takes `Type` (constructor function) and a `value` and returns michael@0: // `true` if `value` is instance of the given `Type`. Function is michael@0: // implicitly curried this allows predicate generation by calling michael@0: // function with just first argument. michael@0: const isInstance = curry((Type, value) => value instanceof Type); michael@0: exports.isInstance = isInstance; michael@0: michael@0: /* michael@0: * Takes a funtion and returns a wrapped function that returns `this` michael@0: */ michael@0: const chainable = f => derive(function(...args) { michael@0: f.apply(this, args); michael@0: return this; michael@0: }, f); michael@0: exports.chainable = chainable; michael@0: exports.chain = michael@0: deprecateFunction(chainable, "Function `chain` was renamed to `chainable`"); michael@0: michael@0: // Functions takes `expected` and `actual` values and returns `true` if michael@0: // `expected === actual`. Returns curried function if called with less then michael@0: // two arguments. michael@0: // michael@0: // [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ] michael@0: const is = curry((expected, actual) => actual === expected); michael@0: exports.is = is; michael@0: michael@0: const isnt = complement(is); michael@0: exports.isnt = isnt; michael@0: michael@0: /** michael@0: * From underscore's `_.debounce` michael@0: * http://underscorejs.org michael@0: * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors michael@0: * Underscore may be freely distributed under the MIT license. michael@0: */ michael@0: const debounce = function debounce (fn, wait) { michael@0: let timeout, args, context, timestamp, result; michael@0: michael@0: let later = function () { michael@0: let last = Date.now() - timestamp; michael@0: if (last < wait) { michael@0: timeout = setTimeout(later, wait - last); michael@0: } else { michael@0: timeout = null; michael@0: result = fn.apply(context, args); michael@0: context = args = null; michael@0: } michael@0: }; michael@0: michael@0: return function (...aArgs) { michael@0: context = this; michael@0: args = aArgs; michael@0: timestamp = Date.now(); michael@0: if (!timeout) { michael@0: timeout = setTimeout(later, wait); michael@0: } michael@0: michael@0: return result; michael@0: }; michael@0: }; michael@0: exports.debounce = debounce; michael@0: michael@0: /** michael@0: * From underscore's `_.throttle` michael@0: * http://underscorejs.org michael@0: * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors michael@0: * Underscore may be freely distributed under the MIT license. michael@0: */ michael@0: const throttle = function throttle (func, wait, options) { michael@0: let context, args, result; michael@0: let timeout = null; michael@0: let previous = 0; michael@0: options || (options = {}); michael@0: let later = function() { michael@0: previous = options.leading === false ? 0 : Date.now(); michael@0: timeout = null; michael@0: result = func.apply(context, args); michael@0: context = args = null; michael@0: }; michael@0: return function() { michael@0: let now = Date.now(); michael@0: if (!previous && options.leading === false) previous = now; michael@0: let remaining = wait - (now - previous); michael@0: context = this; michael@0: args = arguments; michael@0: if (remaining <= 0) { michael@0: clearTimeout(timeout); michael@0: timeout = null; michael@0: previous = now; michael@0: result = func.apply(context, args); michael@0: context = args = null; michael@0: } else if (!timeout && options.trailing !== false) { michael@0: timeout = setTimeout(later, remaining); michael@0: } michael@0: return result; michael@0: }; michael@0: }; michael@0: exports.throttle = throttle;