summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/lang/functional/core.js
blob: 0d9143364a36fcbcd3f3e0880b6278bff6e201ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Disclaimer: Some of the functions in this module implement APIs from
// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
// those goes to him.

"use strict";

module.metadata = {
  "stability": "unstable"
}
const { arity, name, derive, invoke } = require("./helpers");

/**
 * Takes variadic numeber of functions and returns composed one.
 * Returned function pushes `this` pseudo-variable to the head
 * of the passed arguments and invokes all the functions from
 * left to right passing same arguments to them. Composite function
 * returns return value of the right most funciton.
 */
const method = (...lambdas) => {
  return function method(...args) {
    args.unshift(this);
    return lambdas.reduce((_, lambda) => lambda.apply(this, args),
                          void(0));
  };
};
exports.method = method;

/**
 * Invokes `callee` by passing `params` as an arguments and `self` as `this`
 * pseudo-variable. Returns value that is returned by a callee.
 * @param {Function} callee
 *    Function to invoke.
 * @param {Array} params
 *    Arguments to invoke function with.
 * @param {Object} self
 *    Object to be passed as a `this` pseudo variable.
 */
exports.invoke = invoke;

/**
 * Takes a function and bind values to one or more arguments, returning a new
 * function of smaller arity.
 *
 * @param {Function} fn
 *    The function to partial
 *
 * @returns The new function with binded values
 */
const partial = (f, ...curried) => {
  if (typeof(f) !== "function")
    throw new TypeError(String(f) + " is not a function");

  let fn = derive(function(...args) {
    return f.apply(this, curried.concat(args));
  }, f);
  fn.arity = arity(f) - curried.length;
  return fn;
};
exports.partial = partial;

/**
 * Returns function with implicit currying, which will continue currying until
 * expected number of argument is collected. Expected number of arguments is
 * determined by `fn.length`. Using this with variadic functions is stupid,
 * so don't do it.
 *
 * @examples
 *
 * var sum = curry(function(a, b) {
 *   return a + b
 * })
 * console.log(sum(2, 2)) // 4
 * console.log(sum(2)(4)) // 6
 */
const curry = new function() {
  const currier = (fn, arity, params) => {
    // Function either continues to curry arguments or executes function
    // if desired arguments have being collected.
    const curried = function(...input) {
      // Prepend all curried arguments to the given arguments.
      if (params) input.unshift.apply(input, params);
      // If expected number of arguments has being collected invoke fn,
      // othrewise return curried version Otherwise continue curried.
      return (input.length >= arity) ? fn.apply(this, input) :
             currier(fn, arity, input);
    };
    curried.arity = arity - (params ? params.length : 0);

    return curried;
  };

  return fn => currier(fn, arity(fn));
};
exports.curry = curry;

/**
 * Returns the composition of a list of functions, where each function consumes
 * the return value of the function that follows. In math terms, composing the
 * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
 * @example
 *
 *   var greet = function(name) { return "hi: " + name; };
 *   var exclaim = function(statement) { return statement + "!"; };
 *   var welcome = compose(exclaim, greet);
 *
 *   welcome('moe');    // => 'hi: moe!'
 */
function compose(...lambdas) {
  return function composed(...args) {
    let index = lambdas.length;
    while (0 <= --index)
      args = [lambdas[index].apply(this, args)];

    return args[0];
  };
}
exports.compose = compose;

/*
 * Returns the first function passed as an argument to the second,
 * allowing you to adjust arguments, run code before and after, and
 * conditionally execute the original function.
 * @example
 *
 *  var hello = function(name) { return "hello: " + name; };
 *  hello = wrap(hello, function(f) {
 *    return "before, " + f("moe") + ", after";
 *  });
 *
 *  hello();    // => 'before, hello: moe, after'
 */
const wrap = (f, wrapper) => derive(function wrapped(...args) {
  return wrapper.apply(this, [f].concat(args));
}, f);
exports.wrap = wrap;

/**
 * Returns the same value that is used as the argument. In math: f(x) = x
 */
const identity = value => value;
exports.identity = identity;

/**
 * Memoizes a given function by caching the computed result. Useful for
 * speeding up slow-running computations. If passed an optional hashFunction,
 * it will be used to compute the hash key for storing the result, based on
 * the arguments to the original function. The default hashFunction just uses
 * the first argument to the memoized function as the key.
 */
const memoize = (f, hasher) => {
  let memo = Object.create(null);
  let cache = new WeakMap();
  hasher = hasher || identity;
  return derive(function memoizer(...args) {
    const key = hasher.apply(this, args);
    const type = typeof(key);
    if (key && (type === "object" || type === "function")) {
      if (!cache.has(key))
        cache.set(key, f.apply(this, args));
      return cache.get(key);
    }
    else {
      if (!(key in memo))
        memo[key] = f.apply(this, args);
      return memo[key];
    }
  }, f);
};
exports.memoize = memoize;

/*
 * Creates a version of the function that can only be called one time. Repeated
 * calls to the modified function will have no effect, returning the value from
 * the original call. Useful for initialization functions, instead of having to
 * set a boolean flag and then check it later.
 */
const once = f => {
  let ran = false, cache;
  return derive(function(...args) {
    return ran ? cache : (ran = true, cache = f.apply(this, args));
  }, f);
};
exports.once = once;
// export cache as once will may be conflicting with event once a lot.
exports.cache = once;

// Takes a `f` function and returns a function that takes the same
// arguments as `f`, has the same effects, if any, and returns the
// opposite truth value.
const complement = f => derive(function(...args) {
  return args.length < arity(f) ? complement(partial(f, ...args)) :
         !f.apply(this, args);
}, f);
exports.complement = complement;

// Constructs function that returns `x` no matter what is it
// invoked with.
const constant = x => _ => x;
exports.constant = constant;

// Takes `p` predicate, `consequent` function and an optional
// `alternate` function and composes function that returns
// application of arguments over `consequent` if application over
// `p` is `true` otherwise returns application over `alternate`.
// If `alternate` is not a function returns `undefined`.
const when = (p, consequent, alternate) => {
  if (typeof(alternate) !== "function" && alternate !== void(0))
    throw TypeError("alternate must be a function");
  if (typeof(consequent) !== "function")
    throw TypeError("consequent must be a function");

  return function(...args) {
    return p.apply(this, args) ?
           consequent.apply(this, args) :
           alternate && alternate.apply(this, args);
  };
};
exports.when = when;

// Apply function that behaves as `apply` does in lisp:
// apply(f, x, [y, z]) => f.apply(f, [x, y, z])
// apply(f, x) => f.apply(f, [x])
const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
exports.apply = apply;

// Returns function identical to given `f` but with flipped order
// of arguments.
const flip = f => derive(function(...args) {
  return f.apply(this, args.reverse());
}, f);
exports.flip = flip;

// Takes field `name` and `target` and returns value of that field.
// If `target` is `null` or `undefined` it would be returned back
// instead of attempt to access it's field. Function is implicitly
// curried, this allows accessor function generation by calling it
// with only `name` argument.
const field = curry((name, target) =>
  // Note: Permisive `==` is intentional.
  target == null ? target : target[name]);
exports.field = field;

// Takes `.` delimited string representing `path` to a nested field
// and a `target` to get it from. For convinience function is
// implicitly curried, there for accessors can be created by invoking
// it with just a `path` argument.
const query = curry((path, target) => {
  const names = path.split(".");
  const count = names.length;
  let index = 0;
  let result = target;
  // Note: Permisive `!=` is intentional.
  while (result != null && index < count) {
    result = result[names[index]];
    index = index + 1;
  }
  return result;
});
exports.query = query;

// Takes `Type` (constructor function) and a `value` and returns
// `true` if `value` is instance of the given `Type`. Function is
// implicitly curried this allows predicate generation by calling
// function with just first argument.
const isInstance = curry((Type, value) => value instanceof Type);
exports.isInstance = isInstance;

/*
 * Takes a funtion and returns a wrapped function that returns `this`
 */
const chainable = f => derive(function(...args) {
  f.apply(this, args);
  return this;
}, f);
exports.chainable = chainable;

// Functions takes `expected` and `actual` values and returns `true` if
// `expected === actual`. Returns curried function if called with less then
// two arguments.
//
// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
const is = curry((expected, actual) => actual === expected);
exports.is = is;

const isnt = complement(is);
exports.isnt = isnt;