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

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 // Disclaimer: Some of the functions in this module implement APIs from
michael@0 6 // Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
michael@0 7 // those goes to him.
michael@0 8
michael@0 9 "use strict";
michael@0 10
michael@0 11 module.metadata = {
michael@0 12 "stability": "unstable"
michael@0 13 };
michael@0 14
michael@0 15 const { deprecateFunction } = require("../util/deprecate");
michael@0 16 const { setImmediate, setTimeout, clearTimeout } = require("../timers");
michael@0 17
michael@0 18 const arity = f => f.arity || f.length;
michael@0 19
michael@0 20 const name = f => f.displayName || f.name;
michael@0 21
michael@0 22 const derive = (f, source) => {
michael@0 23 f.displayName = name(source);
michael@0 24 f.arity = arity(source);
michael@0 25 return f;
michael@0 26 };
michael@0 27
michael@0 28 /**
michael@0 29 * Takes variadic numeber of functions and returns composed one.
michael@0 30 * Returned function pushes `this` pseudo-variable to the head
michael@0 31 * of the passed arguments and invokes all the functions from
michael@0 32 * left to right passing same arguments to them. Composite function
michael@0 33 * returns return value of the right most funciton.
michael@0 34 */
michael@0 35 const method = (...lambdas) => {
michael@0 36 return function method(...args) {
michael@0 37 args.unshift(this);
michael@0 38 return lambdas.reduce((_, lambda) => lambda.apply(this, args),
michael@0 39 void(0));
michael@0 40 };
michael@0 41 };
michael@0 42 exports.method = method;
michael@0 43
michael@0 44 /**
michael@0 45 * Takes a function and returns a wrapped one instead, calling which will call
michael@0 46 * original function in the next turn of event loop. This is basically utility
michael@0 47 * to do `setImmediate(function() { ... })`, with a difference that returned
michael@0 48 * function is reused, instead of creating a new one each time. This also allows
michael@0 49 * to use this functions as event listeners.
michael@0 50 */
michael@0 51 const defer = f => derive(function(...args) {
michael@0 52 setImmediate(invoke, f, args, this);
michael@0 53 }, f);
michael@0 54 exports.defer = defer;
michael@0 55 // Exporting `remit` alias as `defer` may conflict with promises.
michael@0 56 exports.remit = defer;
michael@0 57
michael@0 58 /**
michael@0 59 * Invokes `callee` by passing `params` as an arguments and `self` as `this`
michael@0 60 * pseudo-variable. Returns value that is returned by a callee.
michael@0 61 * @param {Function} callee
michael@0 62 * Function to invoke.
michael@0 63 * @param {Array} params
michael@0 64 * Arguments to invoke function with.
michael@0 65 * @param {Object} self
michael@0 66 * Object to be passed as a `this` pseudo variable.
michael@0 67 */
michael@0 68 const invoke = (callee, params, self) => callee.apply(self, params);
michael@0 69 exports.invoke = invoke;
michael@0 70
michael@0 71 /**
michael@0 72 * Takes a function and bind values to one or more arguments, returning a new
michael@0 73 * function of smaller arity.
michael@0 74 *
michael@0 75 * @param {Function} fn
michael@0 76 * The function to partial
michael@0 77 *
michael@0 78 * @returns The new function with binded values
michael@0 79 */
michael@0 80 const partial = (f, ...curried) => {
michael@0 81 if (typeof(f) !== "function")
michael@0 82 throw new TypeError(String(f) + " is not a function");
michael@0 83
michael@0 84 let fn = derive(function(...args) {
michael@0 85 return f.apply(this, curried.concat(args));
michael@0 86 }, f);
michael@0 87 fn.arity = arity(f) - curried.length;
michael@0 88 return fn;
michael@0 89 };
michael@0 90 exports.partial = partial;
michael@0 91
michael@0 92 /**
michael@0 93 * Returns function with implicit currying, which will continue currying until
michael@0 94 * expected number of argument is collected. Expected number of arguments is
michael@0 95 * determined by `fn.length`. Using this with variadic functions is stupid,
michael@0 96 * so don't do it.
michael@0 97 *
michael@0 98 * @examples
michael@0 99 *
michael@0 100 * var sum = curry(function(a, b) {
michael@0 101 * return a + b
michael@0 102 * })
michael@0 103 * console.log(sum(2, 2)) // 4
michael@0 104 * console.log(sum(2)(4)) // 6
michael@0 105 */
michael@0 106 const curry = new function() {
michael@0 107 const currier = (fn, arity, params) => {
michael@0 108 // Function either continues to curry arguments or executes function
michael@0 109 // if desired arguments have being collected.
michael@0 110 const curried = function(...input) {
michael@0 111 // Prepend all curried arguments to the given arguments.
michael@0 112 if (params) input.unshift.apply(input, params);
michael@0 113 // If expected number of arguments has being collected invoke fn,
michael@0 114 // othrewise return curried version Otherwise continue curried.
michael@0 115 return (input.length >= arity) ? fn.apply(this, input) :
michael@0 116 currier(fn, arity, input);
michael@0 117 };
michael@0 118 curried.arity = arity - (params ? params.length : 0);
michael@0 119
michael@0 120 return curried;
michael@0 121 };
michael@0 122
michael@0 123 return fn => currier(fn, arity(fn));
michael@0 124 };
michael@0 125 exports.curry = curry;
michael@0 126
michael@0 127 /**
michael@0 128 * Returns the composition of a list of functions, where each function consumes
michael@0 129 * the return value of the function that follows. In math terms, composing the
michael@0 130 * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
michael@0 131 * @example
michael@0 132 *
michael@0 133 * var greet = function(name) { return "hi: " + name; };
michael@0 134 * var exclaim = function(statement) { return statement + "!"; };
michael@0 135 * var welcome = compose(exclaim, greet);
michael@0 136 *
michael@0 137 * welcome('moe'); // => 'hi: moe!'
michael@0 138 */
michael@0 139 function compose(...lambdas) {
michael@0 140 return function composed(...args) {
michael@0 141 let index = lambdas.length;
michael@0 142 while (0 <= --index)
michael@0 143 args = [lambdas[index].apply(this, args)];
michael@0 144
michael@0 145 return args[0];
michael@0 146 };
michael@0 147 }
michael@0 148 exports.compose = compose;
michael@0 149
michael@0 150 /*
michael@0 151 * Returns the first function passed as an argument to the second,
michael@0 152 * allowing you to adjust arguments, run code before and after, and
michael@0 153 * conditionally execute the original function.
michael@0 154 * @example
michael@0 155 *
michael@0 156 * var hello = function(name) { return "hello: " + name; };
michael@0 157 * hello = wrap(hello, function(f) {
michael@0 158 * return "before, " + f("moe") + ", after";
michael@0 159 * });
michael@0 160 *
michael@0 161 * hello(); // => 'before, hello: moe, after'
michael@0 162 */
michael@0 163 const wrap = (f, wrapper) => derive(function wrapped(...args) {
michael@0 164 return wrapper.apply(this, [f].concat(args));
michael@0 165 }, f);
michael@0 166 exports.wrap = wrap;
michael@0 167
michael@0 168 /**
michael@0 169 * Returns the same value that is used as the argument. In math: f(x) = x
michael@0 170 */
michael@0 171 const identity = value => value;
michael@0 172 exports.identity = identity;
michael@0 173
michael@0 174 /**
michael@0 175 * Memoizes a given function by caching the computed result. Useful for
michael@0 176 * speeding up slow-running computations. If passed an optional hashFunction,
michael@0 177 * it will be used to compute the hash key for storing the result, based on
michael@0 178 * the arguments to the original function. The default hashFunction just uses
michael@0 179 * the first argument to the memoized function as the key.
michael@0 180 */
michael@0 181 const memoize = (f, hasher) => {
michael@0 182 let memo = Object.create(null);
michael@0 183 let cache = new WeakMap();
michael@0 184 hasher = hasher || identity;
michael@0 185 return derive(function memoizer(...args) {
michael@0 186 const key = hasher.apply(this, args);
michael@0 187 const type = typeof(key);
michael@0 188 if (key && (type === "object" || type === "function")) {
michael@0 189 if (!cache.has(key))
michael@0 190 cache.set(key, f.apply(this, args));
michael@0 191 return cache.get(key);
michael@0 192 }
michael@0 193 else {
michael@0 194 if (!(key in memo))
michael@0 195 memo[key] = f.apply(this, args);
michael@0 196 return memo[key];
michael@0 197 }
michael@0 198 }, f);
michael@0 199 };
michael@0 200 exports.memoize = memoize;
michael@0 201
michael@0 202 /**
michael@0 203 * Much like setTimeout, invokes function after wait milliseconds. If you pass
michael@0 204 * the optional arguments, they will be forwarded on to the function when it is
michael@0 205 * invoked.
michael@0 206 */
michael@0 207 const delay = function delay(f, ms, ...args) {
michael@0 208 setTimeout(() => f.apply(this, args), ms);
michael@0 209 };
michael@0 210 exports.delay = delay;
michael@0 211
michael@0 212 /**
michael@0 213 * Creates a version of the function that can only be called one time. Repeated
michael@0 214 * calls to the modified function will have no effect, returning the value from
michael@0 215 * the original call. Useful for initialization functions, instead of having to
michael@0 216 * set a boolean flag and then check it later.
michael@0 217 */
michael@0 218 const once = f => {
michael@0 219 let ran = false, cache;
michael@0 220 return derive(function(...args) {
michael@0 221 return ran ? cache : (ran = true, cache = f.apply(this, args));
michael@0 222 }, f);
michael@0 223 };
michael@0 224 exports.once = once;
michael@0 225 // export cache as once will may be conflicting with event once a lot.
michael@0 226 exports.cache = once;
michael@0 227
michael@0 228 // Takes a `f` function and returns a function that takes the same
michael@0 229 // arguments as `f`, has the same effects, if any, and returns the
michael@0 230 // opposite truth value.
michael@0 231 const complement = f => derive(function(...args) {
michael@0 232 return args.length < arity(f) ? complement(partial(f, ...args)) :
michael@0 233 !f.apply(this, args);
michael@0 234 }, f);
michael@0 235 exports.complement = complement;
michael@0 236
michael@0 237 // Constructs function that returns `x` no matter what is it
michael@0 238 // invoked with.
michael@0 239 const constant = x => _ => x;
michael@0 240 exports.constant = constant;
michael@0 241
michael@0 242 // Takes `p` predicate, `consequent` function and an optional
michael@0 243 // `alternate` function and composes function that returns
michael@0 244 // application of arguments over `consequent` if application over
michael@0 245 // `p` is `true` otherwise returns application over `alternate`.
michael@0 246 // If `alternate` is not a function returns `undefined`.
michael@0 247 const when = (p, consequent, alternate) => {
michael@0 248 if (typeof(alternate) !== "function" && alternate !== void(0))
michael@0 249 throw TypeError("alternate must be a function");
michael@0 250 if (typeof(consequent) !== "function")
michael@0 251 throw TypeError("consequent must be a function");
michael@0 252
michael@0 253 return function(...args) {
michael@0 254 return p.apply(this, args) ?
michael@0 255 consequent.apply(this, args) :
michael@0 256 alternate && alternate.apply(this, args);
michael@0 257 };
michael@0 258 };
michael@0 259 exports.when = when;
michael@0 260
michael@0 261 // Apply function that behaves as `apply` does in lisp:
michael@0 262 // apply(f, x, [y, z]) => f.apply(f, [x, y, z])
michael@0 263 // apply(f, x) => f.apply(f, [x])
michael@0 264 const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
michael@0 265 exports.apply = apply;
michael@0 266
michael@0 267 // Returns function identical to given `f` but with flipped order
michael@0 268 // of arguments.
michael@0 269 const flip = f => derive(function(...args) {
michael@0 270 return f.apply(this, args.reverse());
michael@0 271 }, f);
michael@0 272 exports.flip = flip;
michael@0 273
michael@0 274
michael@0 275 // Takes field `name` and `target` and returns value of that field.
michael@0 276 // If `target` is `null` or `undefined` it would be returned back
michael@0 277 // instead of attempt to access it's field. Function is implicitly
michael@0 278 // curried, this allows accessor function generation by calling it
michael@0 279 // with only `name` argument.
michael@0 280 const field = curry((name, target) =>
michael@0 281 // Note: Permisive `==` is intentional.
michael@0 282 target == null ? target : target[name]);
michael@0 283 exports.field = field;
michael@0 284
michael@0 285 // Takes `.` delimited string representing `path` to a nested field
michael@0 286 // and a `target` to get it from. For convinience function is
michael@0 287 // implicitly curried, there for accessors can be created by invoking
michael@0 288 // it with just a `path` argument.
michael@0 289 const query = curry((path, target) => {
michael@0 290 const names = path.split(".");
michael@0 291 const count = names.length;
michael@0 292 let index = 0;
michael@0 293 let result = target;
michael@0 294 // Note: Permisive `!=` is intentional.
michael@0 295 while (result != null && index < count) {
michael@0 296 result = result[names[index]];
michael@0 297 index = index + 1;
michael@0 298 }
michael@0 299 return result;
michael@0 300 });
michael@0 301 exports.query = query;
michael@0 302
michael@0 303 // Takes `Type` (constructor function) and a `value` and returns
michael@0 304 // `true` if `value` is instance of the given `Type`. Function is
michael@0 305 // implicitly curried this allows predicate generation by calling
michael@0 306 // function with just first argument.
michael@0 307 const isInstance = curry((Type, value) => value instanceof Type);
michael@0 308 exports.isInstance = isInstance;
michael@0 309
michael@0 310 /*
michael@0 311 * Takes a funtion and returns a wrapped function that returns `this`
michael@0 312 */
michael@0 313 const chainable = f => derive(function(...args) {
michael@0 314 f.apply(this, args);
michael@0 315 return this;
michael@0 316 }, f);
michael@0 317 exports.chainable = chainable;
michael@0 318 exports.chain =
michael@0 319 deprecateFunction(chainable, "Function `chain` was renamed to `chainable`");
michael@0 320
michael@0 321 // Functions takes `expected` and `actual` values and returns `true` if
michael@0 322 // `expected === actual`. Returns curried function if called with less then
michael@0 323 // two arguments.
michael@0 324 //
michael@0 325 // [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
michael@0 326 const is = curry((expected, actual) => actual === expected);
michael@0 327 exports.is = is;
michael@0 328
michael@0 329 const isnt = complement(is);
michael@0 330 exports.isnt = isnt;
michael@0 331
michael@0 332 /**
michael@0 333 * From underscore's `_.debounce`
michael@0 334 * http://underscorejs.org
michael@0 335 * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
michael@0 336 * Underscore may be freely distributed under the MIT license.
michael@0 337 */
michael@0 338 const debounce = function debounce (fn, wait) {
michael@0 339 let timeout, args, context, timestamp, result;
michael@0 340
michael@0 341 let later = function () {
michael@0 342 let last = Date.now() - timestamp;
michael@0 343 if (last < wait) {
michael@0 344 timeout = setTimeout(later, wait - last);
michael@0 345 } else {
michael@0 346 timeout = null;
michael@0 347 result = fn.apply(context, args);
michael@0 348 context = args = null;
michael@0 349 }
michael@0 350 };
michael@0 351
michael@0 352 return function (...aArgs) {
michael@0 353 context = this;
michael@0 354 args = aArgs;
michael@0 355 timestamp = Date.now();
michael@0 356 if (!timeout) {
michael@0 357 timeout = setTimeout(later, wait);
michael@0 358 }
michael@0 359
michael@0 360 return result;
michael@0 361 };
michael@0 362 };
michael@0 363 exports.debounce = debounce;
michael@0 364
michael@0 365 /**
michael@0 366 * From underscore's `_.throttle`
michael@0 367 * http://underscorejs.org
michael@0 368 * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
michael@0 369 * Underscore may be freely distributed under the MIT license.
michael@0 370 */
michael@0 371 const throttle = function throttle (func, wait, options) {
michael@0 372 let context, args, result;
michael@0 373 let timeout = null;
michael@0 374 let previous = 0;
michael@0 375 options || (options = {});
michael@0 376 let later = function() {
michael@0 377 previous = options.leading === false ? 0 : Date.now();
michael@0 378 timeout = null;
michael@0 379 result = func.apply(context, args);
michael@0 380 context = args = null;
michael@0 381 };
michael@0 382 return function() {
michael@0 383 let now = Date.now();
michael@0 384 if (!previous && options.leading === false) previous = now;
michael@0 385 let remaining = wait - (now - previous);
michael@0 386 context = this;
michael@0 387 args = arguments;
michael@0 388 if (remaining <= 0) {
michael@0 389 clearTimeout(timeout);
michael@0 390 timeout = null;
michael@0 391 previous = now;
michael@0 392 result = func.apply(context, args);
michael@0 393 context = args = null;
michael@0 394 } else if (!timeout && options.trailing !== false) {
michael@0 395 timeout = setTimeout(later, remaining);
michael@0 396 }
michael@0 397 return result;
michael@0 398 };
michael@0 399 };
michael@0 400 exports.throttle = throttle;

mercurial