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;