addon-sdk/source/lib/sdk/lang/functional.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/addon-sdk/source/lib/sdk/lang/functional.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,400 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +// Disclaimer: Some of the functions in this module implement APIs from
     1.9 +// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
    1.10 +// those goes to him.
    1.11 +
    1.12 +"use strict";
    1.13 +
    1.14 +module.metadata = {
    1.15 +  "stability": "unstable"
    1.16 +};
    1.17 +
    1.18 +const { deprecateFunction } = require("../util/deprecate");
    1.19 +const { setImmediate, setTimeout, clearTimeout } = require("../timers");
    1.20 +
    1.21 +const arity = f => f.arity || f.length;
    1.22 +
    1.23 +const name = f => f.displayName || f.name;
    1.24 +
    1.25 +const derive = (f, source) => {
    1.26 +  f.displayName = name(source);
    1.27 +  f.arity = arity(source);
    1.28 +  return f;
    1.29 +};
    1.30 +
    1.31 +/**
    1.32 + * Takes variadic numeber of functions and returns composed one.
    1.33 + * Returned function pushes `this` pseudo-variable to the head
    1.34 + * of the passed arguments and invokes all the functions from
    1.35 + * left to right passing same arguments to them. Composite function
    1.36 + * returns return value of the right most funciton.
    1.37 + */
    1.38 +const method = (...lambdas) => {
    1.39 +  return function method(...args) {
    1.40 +    args.unshift(this);
    1.41 +    return lambdas.reduce((_, lambda) => lambda.apply(this, args),
    1.42 +                          void(0));
    1.43 +  };
    1.44 +};
    1.45 +exports.method = method;
    1.46 +
    1.47 +/**
    1.48 + * Takes a function and returns a wrapped one instead, calling which will call
    1.49 + * original function in the next turn of event loop. This is basically utility
    1.50 + * to do `setImmediate(function() { ... })`, with a difference that returned
    1.51 + * function is reused, instead of creating a new one each time. This also allows
    1.52 + * to use this functions as event listeners.
    1.53 + */
    1.54 +const defer = f => derive(function(...args) {
    1.55 +  setImmediate(invoke, f, args, this);
    1.56 +}, f);
    1.57 +exports.defer = defer;
    1.58 +// Exporting `remit` alias as `defer` may conflict with promises.
    1.59 +exports.remit = defer;
    1.60 +
    1.61 +/**
    1.62 + * Invokes `callee` by passing `params` as an arguments and `self` as `this`
    1.63 + * pseudo-variable. Returns value that is returned by a callee.
    1.64 + * @param {Function} callee
    1.65 + *    Function to invoke.
    1.66 + * @param {Array} params
    1.67 + *    Arguments to invoke function with.
    1.68 + * @param {Object} self
    1.69 + *    Object to be passed as a `this` pseudo variable.
    1.70 + */
    1.71 +const invoke = (callee, params, self) => callee.apply(self, params);
    1.72 +exports.invoke = invoke;
    1.73 +
    1.74 +/**
    1.75 + * Takes a function and bind values to one or more arguments, returning a new
    1.76 + * function of smaller arity.
    1.77 + *
    1.78 + * @param {Function} fn
    1.79 + *    The function to partial
    1.80 + *
    1.81 + * @returns The new function with binded values
    1.82 + */
    1.83 +const partial = (f, ...curried) => {
    1.84 +  if (typeof(f) !== "function")
    1.85 +    throw new TypeError(String(f) + " is not a function");
    1.86 +
    1.87 +  let fn = derive(function(...args) {
    1.88 +    return f.apply(this, curried.concat(args));
    1.89 +  }, f);
    1.90 +  fn.arity = arity(f) - curried.length;
    1.91 +  return fn;
    1.92 +};
    1.93 +exports.partial = partial;
    1.94 +
    1.95 +/**
    1.96 + * Returns function with implicit currying, which will continue currying until
    1.97 + * expected number of argument is collected. Expected number of arguments is
    1.98 + * determined by `fn.length`. Using this with variadic functions is stupid,
    1.99 + * so don't do it.
   1.100 + *
   1.101 + * @examples
   1.102 + *
   1.103 + * var sum = curry(function(a, b) {
   1.104 + *   return a + b
   1.105 + * })
   1.106 + * console.log(sum(2, 2)) // 4
   1.107 + * console.log(sum(2)(4)) // 6
   1.108 + */
   1.109 +const curry = new function() {
   1.110 +  const currier = (fn, arity, params) => {
   1.111 +    // Function either continues to curry arguments or executes function
   1.112 +    // if desired arguments have being collected.
   1.113 +    const curried = function(...input) {
   1.114 +      // Prepend all curried arguments to the given arguments.
   1.115 +      if (params) input.unshift.apply(input, params);
   1.116 +      // If expected number of arguments has being collected invoke fn,
   1.117 +      // othrewise return curried version Otherwise continue curried.
   1.118 +      return (input.length >= arity) ? fn.apply(this, input) :
   1.119 +             currier(fn, arity, input);
   1.120 +    };
   1.121 +    curried.arity = arity - (params ? params.length : 0);
   1.122 +
   1.123 +    return curried;
   1.124 +  };
   1.125 +
   1.126 +  return fn => currier(fn, arity(fn));
   1.127 +};
   1.128 +exports.curry = curry;
   1.129 +
   1.130 +/**
   1.131 + * Returns the composition of a list of functions, where each function consumes
   1.132 + * the return value of the function that follows. In math terms, composing the
   1.133 + * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
   1.134 + * @example
   1.135 + *
   1.136 + *   var greet = function(name) { return "hi: " + name; };
   1.137 + *   var exclaim = function(statement) { return statement + "!"; };
   1.138 + *   var welcome = compose(exclaim, greet);
   1.139 + *
   1.140 + *   welcome('moe');    // => 'hi: moe!'
   1.141 + */
   1.142 +function compose(...lambdas) {
   1.143 +  return function composed(...args) {
   1.144 +    let index = lambdas.length;
   1.145 +    while (0 <= --index)
   1.146 +      args = [lambdas[index].apply(this, args)];
   1.147 +
   1.148 +    return args[0];
   1.149 +  };
   1.150 +}
   1.151 +exports.compose = compose;
   1.152 +
   1.153 +/*
   1.154 + * Returns the first function passed as an argument to the second,
   1.155 + * allowing you to adjust arguments, run code before and after, and
   1.156 + * conditionally execute the original function.
   1.157 + * @example
   1.158 + *
   1.159 + *  var hello = function(name) { return "hello: " + name; };
   1.160 + *  hello = wrap(hello, function(f) {
   1.161 + *    return "before, " + f("moe") + ", after";
   1.162 + *  });
   1.163 + *
   1.164 + *  hello();    // => 'before, hello: moe, after'
   1.165 + */
   1.166 +const wrap = (f, wrapper) => derive(function wrapped(...args) {
   1.167 +  return wrapper.apply(this, [f].concat(args));
   1.168 +}, f);
   1.169 +exports.wrap = wrap;
   1.170 +
   1.171 +/**
   1.172 + * Returns the same value that is used as the argument. In math: f(x) = x
   1.173 + */
   1.174 +const identity = value => value;
   1.175 +exports.identity = identity;
   1.176 +
   1.177 +/**
   1.178 + * Memoizes a given function by caching the computed result. Useful for
   1.179 + * speeding up slow-running computations. If passed an optional hashFunction,
   1.180 + * it will be used to compute the hash key for storing the result, based on
   1.181 + * the arguments to the original function. The default hashFunction just uses
   1.182 + * the first argument to the memoized function as the key.
   1.183 + */
   1.184 +const memoize = (f, hasher) => {
   1.185 +  let memo = Object.create(null);
   1.186 +  let cache = new WeakMap();
   1.187 +  hasher = hasher || identity;
   1.188 +  return derive(function memoizer(...args) {
   1.189 +    const key = hasher.apply(this, args);
   1.190 +    const type = typeof(key);
   1.191 +    if (key && (type === "object" || type === "function")) {
   1.192 +      if (!cache.has(key))
   1.193 +        cache.set(key, f.apply(this, args));
   1.194 +      return cache.get(key);
   1.195 +    }
   1.196 +    else {
   1.197 +      if (!(key in memo))
   1.198 +        memo[key] = f.apply(this, args);
   1.199 +      return memo[key];
   1.200 +    }
   1.201 +  }, f);
   1.202 +};
   1.203 +exports.memoize = memoize;
   1.204 +
   1.205 +/**
   1.206 + * Much like setTimeout, invokes function after wait milliseconds. If you pass
   1.207 + * the optional arguments, they will be forwarded on to the function when it is
   1.208 + * invoked.
   1.209 + */
   1.210 +const delay = function delay(f, ms, ...args) {
   1.211 +  setTimeout(() => f.apply(this, args), ms);
   1.212 +};
   1.213 +exports.delay = delay;
   1.214 +
   1.215 +/**
   1.216 + * Creates a version of the function that can only be called one time. Repeated
   1.217 + * calls to the modified function will have no effect, returning the value from
   1.218 + * the original call. Useful for initialization functions, instead of having to
   1.219 + * set a boolean flag and then check it later.
   1.220 + */
   1.221 +const once = f => {
   1.222 +  let ran = false, cache;
   1.223 +  return derive(function(...args) {
   1.224 +    return ran ? cache : (ran = true, cache = f.apply(this, args));
   1.225 +  }, f);
   1.226 +};
   1.227 +exports.once = once;
   1.228 +// export cache as once will may be conflicting with event once a lot.
   1.229 +exports.cache = once;
   1.230 +
   1.231 +// Takes a `f` function and returns a function that takes the same
   1.232 +// arguments as `f`, has the same effects, if any, and returns the
   1.233 +// opposite truth value.
   1.234 +const complement = f => derive(function(...args) {
   1.235 +  return args.length < arity(f) ? complement(partial(f, ...args)) :
   1.236 +         !f.apply(this, args);
   1.237 +}, f);
   1.238 +exports.complement = complement;
   1.239 +
   1.240 +// Constructs function that returns `x` no matter what is it
   1.241 +// invoked with.
   1.242 +const constant = x => _ => x;
   1.243 +exports.constant = constant;
   1.244 +
   1.245 +// Takes `p` predicate, `consequent` function and an optional
   1.246 +// `alternate` function and composes function that returns
   1.247 +// application of arguments over `consequent` if application over
   1.248 +// `p` is `true` otherwise returns application over `alternate`.
   1.249 +// If `alternate` is not a function returns `undefined`.
   1.250 +const when = (p, consequent, alternate) => {
   1.251 +  if (typeof(alternate) !== "function" && alternate !== void(0))
   1.252 +    throw TypeError("alternate must be a function");
   1.253 +  if (typeof(consequent) !== "function")
   1.254 +    throw TypeError("consequent must be a function");
   1.255 +
   1.256 +  return function(...args) {
   1.257 +    return p.apply(this, args) ?
   1.258 +           consequent.apply(this, args) :
   1.259 +           alternate && alternate.apply(this, args);
   1.260 +  };
   1.261 +};
   1.262 +exports.when = when;
   1.263 +
   1.264 +// Apply function that behaves as `apply` does in lisp:
   1.265 +// apply(f, x, [y, z]) => f.apply(f, [x, y, z])
   1.266 +// apply(f, x) => f.apply(f, [x])
   1.267 +const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
   1.268 +exports.apply = apply;
   1.269 +
   1.270 +// Returns function identical to given `f` but with flipped order
   1.271 +// of arguments.
   1.272 +const flip = f => derive(function(...args) {
   1.273 +  return f.apply(this, args.reverse());
   1.274 +}, f);
   1.275 +exports.flip = flip;
   1.276 +
   1.277 +
   1.278 +// Takes field `name` and `target` and returns value of that field.
   1.279 +// If `target` is `null` or `undefined` it would be returned back
   1.280 +// instead of attempt to access it's field. Function is implicitly
   1.281 +// curried, this allows accessor function generation by calling it
   1.282 +// with only `name` argument.
   1.283 +const field = curry((name, target) =>
   1.284 +  // Note: Permisive `==` is intentional.
   1.285 +  target == null ? target : target[name]);
   1.286 +exports.field = field;
   1.287 +
   1.288 +// Takes `.` delimited string representing `path` to a nested field
   1.289 +// and a `target` to get it from. For convinience function is
   1.290 +// implicitly curried, there for accessors can be created by invoking
   1.291 +// it with just a `path` argument.
   1.292 +const query = curry((path, target) => {
   1.293 +  const names = path.split(".");
   1.294 +  const count = names.length;
   1.295 +  let index = 0;
   1.296 +  let result = target;
   1.297 +  // Note: Permisive `!=` is intentional.
   1.298 +  while (result != null && index < count) {
   1.299 +    result = result[names[index]];
   1.300 +    index = index + 1;
   1.301 +  }
   1.302 +  return result;
   1.303 +});
   1.304 +exports.query = query;
   1.305 +
   1.306 +// Takes `Type` (constructor function) and a `value` and returns
   1.307 +// `true` if `value` is instance of the given `Type`. Function is
   1.308 +// implicitly curried this allows predicate generation by calling
   1.309 +// function with just first argument.
   1.310 +const isInstance = curry((Type, value) => value instanceof Type);
   1.311 +exports.isInstance = isInstance;
   1.312 +
   1.313 +/*
   1.314 + * Takes a funtion and returns a wrapped function that returns `this`
   1.315 + */
   1.316 +const chainable = f => derive(function(...args) {
   1.317 +  f.apply(this, args);
   1.318 +  return this;
   1.319 +}, f);
   1.320 +exports.chainable = chainable;
   1.321 +exports.chain =
   1.322 +  deprecateFunction(chainable, "Function `chain` was renamed to `chainable`");
   1.323 +
   1.324 +// Functions takes `expected` and `actual` values and returns `true` if
   1.325 +// `expected === actual`. Returns curried function if called with less then
   1.326 +// two arguments.
   1.327 +//
   1.328 +// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
   1.329 +const is = curry((expected, actual) => actual === expected);
   1.330 +exports.is = is;
   1.331 +
   1.332 +const isnt = complement(is);
   1.333 +exports.isnt = isnt;
   1.334 +
   1.335 +/**
   1.336 + * From underscore's `_.debounce`
   1.337 + * http://underscorejs.org
   1.338 + * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
   1.339 + * Underscore may be freely distributed under the MIT license.
   1.340 + */
   1.341 +const debounce = function debounce (fn, wait) {
   1.342 +  let timeout, args, context, timestamp, result;
   1.343 +
   1.344 +  let later = function () {
   1.345 +    let last = Date.now() - timestamp;
   1.346 +    if (last < wait) {
   1.347 +      timeout = setTimeout(later, wait - last);
   1.348 +    } else {
   1.349 +      timeout = null;
   1.350 +      result = fn.apply(context, args);
   1.351 +      context = args = null;
   1.352 +    }
   1.353 +  };
   1.354 +
   1.355 +  return function (...aArgs) {
   1.356 +    context = this;
   1.357 +    args = aArgs;
   1.358 +    timestamp  = Date.now();
   1.359 +    if (!timeout) {
   1.360 +      timeout = setTimeout(later, wait);
   1.361 +    }
   1.362 +
   1.363 +    return result;
   1.364 +  };
   1.365 +};
   1.366 +exports.debounce = debounce;
   1.367 +
   1.368 +/**
   1.369 + * From underscore's `_.throttle`
   1.370 + * http://underscorejs.org
   1.371 + * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
   1.372 + * Underscore may be freely distributed under the MIT license.
   1.373 + */
   1.374 +const throttle = function throttle (func, wait, options) {
   1.375 +  let context, args, result;
   1.376 +  let timeout = null;
   1.377 +  let previous = 0;
   1.378 +  options || (options = {});
   1.379 +  let later = function() {
   1.380 +    previous = options.leading === false ? 0 : Date.now();
   1.381 +    timeout = null;
   1.382 +    result = func.apply(context, args);
   1.383 +    context = args = null;
   1.384 +  };
   1.385 +  return function() {
   1.386 +    let now = Date.now();
   1.387 +    if (!previous && options.leading === false) previous = now;
   1.388 +    let remaining = wait - (now - previous);
   1.389 +    context = this;
   1.390 +    args = arguments;
   1.391 +    if (remaining <= 0) {
   1.392 +      clearTimeout(timeout);
   1.393 +      timeout = null;
   1.394 +      previous = now;
   1.395 +      result = func.apply(context, args);
   1.396 +      context = args = null;
   1.397 +    } else if (!timeout && options.trailing !== false) {
   1.398 +      timeout = setTimeout(later, remaining);
   1.399 +    }
   1.400 +    return result;
   1.401 +  };
   1.402 +};
   1.403 +exports.throttle = throttle;

mercurial