Initial import.
[pwl6.git] / src / yuu / yf.js
1 /* Copyright 2014 Yukkuri Games
2 Licensed under the terms of the GNU GPL v2 or later
3 @license http://www.gnu.org/licenses/gpl-2.0.html
4 @source: http://yukkurigames.com/yuu/
5 */
6
7 (function () {
8 "use strict";
9
10 /** yf - yuu foundation
11
12 A collection of tools for functional programming and the kind
13 of sequence (array) manipulations usually associated with
14 functional programming.
15
16 yf doesn't depend on yuu specifically, but may use standard
17 features not yet widely supported provided by yuu/pre.
18 */
19
20 function isFunction (o) {
21 /** Check if a value is a function */
22 return Object.prototype.toString.call(o) === '[object Function]';
23 }
24
25 function isString (s) {
26 /** Check if a value is a string */
27 return Object.prototype.toString.call(s) === "[object String]";
28 }
29
30 function I (x) { return x; }
31
32 function K (x) { return function () { return x; }; }
33
34 /** Get the minimum or maximum of two objects
35
36 The default JavaScript Math.min and Math.max are unsuitable as
37 generic functions. They don't work on strings. Because they
38 support a variable numbers of arguments they don't work
39 correctly when passed directly to higher-order functions that
40 pass "bonus" arguments such as Array.prototype.reduce.
41
42 These functions work on any objects comparable with < and >,
43 and only consider two arguments.
44 */
45 function min (a, b) { return b < a ? b : a; }
46 function max (a, b) { return b > a ? b : a; }
47 function clamp(x, lo, hi) { return max(lo, min(hi, x)); }
48
49 /** Get a property from the this object by name */
50 function getter (key) { return this[key]; }
51
52 /** Set a property value on the this object by name */
53 function setter (key, value) { this[key] = value; }
54
55 function unbind (f) {
56 /** Factor out the special 'this' argument from a function
57
58 unbind(f)(a, b, c) is equivalent to f.call(a, b, c), and
59 you may store the return value for later use without
60 storing the original function.
61 */
62
63 // return f.call.bind(f) is ~15% slower than this closure on
64 // Chrome 31 and negligibly slower on Firefox 25.
65 return function () {
66 return f.call.apply(f, arguments);
67 };
68 }
69
70 /** Unbound versions of useful functions */
71 var slice = unbind(Array.prototype.slice);
72
73 function contains (seq, o) {
74 return Array.prototype.indexOf.call(seq, o) !== -1;
75 }
76
77 function lacks (seq, o) {
78 return !contains(seq, o);
79 }
80
81 /** The first element of a sequence */
82 function head (a) { return a[0]; }
83
84 /** All but the first element of a sequence */
85 function tail (a) { return slice(a, 1); }
86
87 /** The last element of a sequence */
88 function last (a) { return a[a.length - 1]; }
89
90 /** All but the last element of a sequence */
91 function most (a) { return slice(a, 0, -1); }
92
93 /** The length of a sequence */
94 function len (seq) { return seq.length; }
95
96 /** Note that yf's argument order is slightly different from
97 JavaScript's normal order (but the same as most other
98 languages with functional elements).
99
100 This is irrelevant for `fold` and `filter`, but `each` and
101 `map` can take multiple sequences to allow callback functions
102 with arbitrary arity.
103
104 Because of this difference, there is no final `thisArg` as in
105 e.g. the standard Array's forEach. Instead the `this` of
106 the original call is forwarded. For example,
107 someArray.forEach(callback, thisArg)
108 becomes
109 yf.each.call(thisArg, callback, someArray)
110 */
111
112 function foldl (callback, seq, value) {
113 /** Like Array's reduce, but with no extra arguments
114
115 This traverses the sequence in indexed order, low-to-high,
116 and calls the provided function with the accumulated value
117 and the sequence item.
118 */
119 var i = 0;
120 if (arguments.length === 2)
121 value = seq[i++];
122 for (; i < seq.length; ++i)
123 value = callback.call(this, value, seq[i]);
124 return value;
125 }
126
127 function foldr (callback, seq, value) {
128 /** Like foldl, but reversed.
129
130 This traverses the sequence in indexed order, high-to-low,
131 and calls the provided function with the sequence item and
132 the accumulated value.
133 */
134 var i = seq.length - 1;
135 if (arguments.length === 2)
136 value = seq[i--];
137 for (; i >= 0; --i)
138 value = callback.call(this, seq[i], value);
139 return value;
140 }
141
142 function call (f, x) { return f.call(this, x); }
143
144 function compose () {
145 /** Create a function by composing multiple functions
146
147 compose(f, g, h)(x, y, z) is equivalent to f(g(h(x, y, z))).
148 */
149 var inner = last(arguments);
150 var funcs = most(arguments);
151 return function () {
152 return foldr.call(this, call, funcs, inner.apply(this, arguments));
153 };
154 }
155
156 // Generating specialized functions for plucking the nth argument
157 // and passing it to a callback is 100-120x faster than the
158 // generic one in V8.
159 //
160 // So generate specialized functions for small argc, and only
161 // resort to the generic approach when there's many sequences.
162 var CALLS = [];
163
164 /* jshint -W054*/ /* Function constructor is a form of eval */
165 for (var args = []; args.length < 32;
166 args.push(", seqs[" + args.length + "][i]"))
167 CALLS[args.length] = new Function(
168 "callback", "i", "seqs",
169 "return callback.call(this" + args.join("") + ");");
170
171 function lookup (prop) {
172 /** Return a function that gets a particular property */
173
174 // Building this as a closure is ~2-5x faster than writing
175 // a function lookup(prop, o) and binding it.
176 return function (s) { return s[prop]; };
177 }
178
179 function callx (callback, i, seqs) {
180 return callback.apply(this, seqs.map(lookup(i)));
181 }
182
183 function calln (argc) {
184 return CALLS[argc] || callx;
185 }
186
187 function argv (f) {
188 /** Provide a variadic-like version of f
189
190 Arguments passed to the function including and after the
191 final named one are collected and passed as a single
192 sequence (not necessarily Array) value.
193 */
194 var length = f.length;
195 switch (length) {
196 case 0: throw new Error("don't use argv for 0-ary functions");
197 case 1: return function () { return f.call(this, arguments); };
198 default:
199 return function () {
200 arguments[length - 1] = slice(arguments, length - 1);
201 return f.apply(this, arguments);
202 };
203 }
204 }
205
206 var each = argv(function (callback, seqs) {
207 /** Call a function on the given sequences' elements
208
209 If one sequence is longer than the others, `undefined`
210 will be passed for that sequence's argument.
211
212 For example, each(f, [a, b, c], [x, y]) is equivalent to
213 f(a, x);
214 f(b, y);
215 f(c, undefined);
216
217 If you want to pass a thisArg to the callback, use
218 each.call(thisArg, callback, ...).
219 */
220 var length = Math.max.apply(Math, seqs.map(len));
221 var fn = calln(seqs.length);
222 for (var i = 0; i < length; ++i)
223 fn.call(this, callback, i, seqs);
224 });
225
226 var eachr = argv(function (callback, seqs) {
227 /** Call a function on the given sequences' elements, backwards
228
229 If one sequence is longer than the others, `undefined` will be
230 passed for that sequence's argument.
231
232 For example, each(f, [a, b, c], [x, y]) is equivalent to
233 f(c, undefined);
234 f(b, y);
235 f(a, x);
236
237 If you want to pass a thisArg to the callback, use
238 eachr.call(thisArg, callback, ...).
239 */
240 var length = Math.max.apply(Math, seqs.map(len));
241 var fn = calln(seqs.length);
242 for (var i = length - 1; i >= 0; --i)
243 fn.call(this, callback, i, seqs);
244 });
245
246 function pusher (a) {
247 // Ridiculously faster than a.push.bind(a). :(
248 return function (o) { a.push(o); };
249 }
250
251 function map () {
252 /** Build an array by applying a function to sequences
253
254 Similar to Array.prototype.map, but allows passing multiple
255 sequences to call functions of any arity, as with each.
256
257 If you want to pass a thisArg to the callback, use
258 map.call(thisArg, callback, ...).
259 */
260 var a = [];
261 arguments[0] = compose(pusher(a), arguments[0]);
262 each.apply(this, arguments);
263 return a;
264 }
265
266 function mapr () {
267 /** Build an array by applying a function to sequences backwards
268
269 As eachr is to each, so mapr is to map.
270 */
271 var a = [];
272 arguments[0] = compose(pusher(a), arguments[0]);
273 eachr.apply(this, arguments);
274 return a;
275 }
276
277 function filter (callback, seq) {
278 /** Build an array without the elements that fail the predicate
279
280 Like Array.prototype.filter, but if null is passed for the
281 predicate, any false-y values will be removed.
282 */
283 var a = [];
284 callback = callback || I;
285 function _ (o) { if (callback.call(this, o)) a.push(o); }
286 each.call(this, _, seq);
287 return a;
288 }
289
290 function packed (f) {
291 /** Pack arguments to a function into an Array
292
293 packed(f)([a, b, c]) is equivalent to f(a, b, c).
294 */
295 return function (args) { return f.apply(this, args); };
296 }
297
298 function argcd () {
299 /** Create a function that dispatches based on argument count
300
301 Pass a list of functions with varying argument lengths, e.g.
302 var f = argcd(function () { ... },
303 function (a, b) { ... },
304 function (a, b, c) { ... });
305
306 This can be used for function overloading, clearer
307 defaults for positional parameters, or significantly more
308 evil things.
309
310 The functions can also be accessed directly by their
311 argument count, e.g. f[0], f[2], f[3] above.
312
313 If no matching argument count is found for a call, a
314 TypeError is raised.
315 */
316 if (arguments.length === 1)
317 throw new Error("don't use argcd for one function");
318 var table = (function table () {
319 return table[arguments.length].apply(this, arguments);
320 });
321 for (var i = 0; i < arguments.length; ++i)
322 table[arguments[i].length] = arguments[i];
323 return table;
324 }
325
326 var irange = argcd(
327 /** Call the provided callback counting as it goes
328
329 irange(x) counts from [0, x), by 1.
330
331 irange(x, y) counts from [x, y), by 1.
332
333 irange(x, y, s) counts from [x, y), by s.
334 */
335 function (callback, stop) {
336 return irange.call(this, callback, 0, stop, 1);
337 },
338 function (callback, start, stop) {
339 return irange.call(this, callback, start, stop, 1);
340 },
341 function (callback, start, stop, step) {
342 var length = Math.max(Math.ceil((stop - start) / step), 0);
343 for (var i = 0; i < length; ++i)
344 callback.call(this, start + step * i);
345 }
346 );
347
348 function capture1 (ifunc) {
349 /** Build an array from the values of an iterating function
350
351 The elements added to the array are the first arguments
352 the iterating function passes.
353 */
354 return function () {
355 var a = [];
356 var args = slice(arguments);
357 args.unshift(pusher(a));
358 ifunc.apply(this, args);
359 return a;
360 };
361 }
362
363 /** Generate an Array from range of numbers, as irange */
364 var range = capture1(irange);
365
366 function ipairs (callback, o) {
367 /** Call the provided callback with each key and value of the object */
368 each.call(this, function (k) { callback.call(this, k, o[k]); },
369 Object.keys(o));
370 }
371
372 function iprototypeChain (callback, o) {
373 /** Traverse an object and its prototype chain */
374 do {
375 callback.call(this, o);
376 } while ((o = Object.getPrototypeOf(o)));
377 }
378
379 function allKeys (o) {
380 /** Get *ALL* property names. Even unowned non-enumerable ones. */
381 var props = [];
382 iprototypeChain(compose(packed(props.push).bind(props),
383 Object.getOwnPropertyNames),
384 o);
385 return props.sort().filter(function (p, i, a) {
386 return p !== a[i - 1];
387 });
388 }
389
390 var some = argv(function (callback, seqs) {
391 callback = callback || I;
392 var length = Math.max.apply(Math, seqs.map(len));
393 var fn = calln(seqs.length);
394 for (var i = 0; i < length; ++i)
395 if (fn.call(this, callback, i, seqs))
396 return true;
397 return false;
398 });
399
400 var eachrUntil = argv(function (callback, seqs) { // Reverse of `some`
401 callback = callback || I;
402 var length = Math.max.apply(Math, seqs.map(len));
403 var fn = calln(seqs.length);
404 for (var i = length - 1; i >= 0; --i)
405 if (fn.call(this, callback, i, seqs))
406 return true;
407 return false;
408 });
409
410 function not (f) {
411 /** Build a function equivalent to !f(...) */
412 return function () { return !f.apply(this, arguments); };
413 }
414
415 function every () {
416 arguments[0] = not(arguments[0] || I);
417 return !some.apply(this, arguments);
418 }
419
420 function none () {
421 return !some.apply(this, arguments);
422 }
423
424 function repeat (o, n) {
425 return (new Array(n)).fill(o);
426 }
427
428 function arrayify (o) {
429 /** Equivalent to [].concat(o) but faster */
430 return Array.isArray(o) ? o : [o];
431 }
432
433 function stash (key, value, a) {
434 each(function (o) { o[key] = value; }, a);
435 }
436
437 function without (seq, o) {
438 if (arguments.length === 2)
439 return filter(function (a) { return a !== o; }, seq);
440 var os = slice(arguments, 1);
441 return filter(lacks.bind(null, os), seq);
442 }
443
444 function construct (ctr, args) {
445 args = slice(args);
446 args.unshift(ctr);
447 return new (Function.prototype.bind.apply(ctr, args))();
448 }
449
450 function new_ (ctr) {
451 return function () { return construct(ctr, arguments); };
452 }
453
454 function counter (start) {
455 var i = +(start || 0);
456 return function () { return i++; };
457 }
458
459 function volatile (f) {
460 return { valueOf: f };
461 }
462
463 function transform (callback, seq) {
464 irange.call(this, function (i) {
465 seq[i] = callback.call(this, seq[i]);
466 }, seq.length);
467 }
468
469 function mapValues (callback, o) {
470 var r = {};
471 for (var k in o)
472 r[k] = callback.call(this, o[k]);
473 return r;
474 }
475
476 function insertBefore (seq, o, before) {
477 var idx = seq.lastIndexOf(before);
478 if (idx === -1)
479 seq.push(o);
480 else
481 seq.splice(idx, 0, o);
482 return seq;
483 }
484
485 function seqEqual (a, b) {
486 if (a.length !== b.length)
487 return false;
488 for (var i = 0; i < a.length; ++i)
489 if (a[i] !== b[i])
490 return false;
491 return true;
492 }
493
494 function debounce (callback, wait) {
495 wait = wait || 100;
496 var this_ = null;
497 var args = null;
498 var id = null;
499
500 function _ () {
501 callback.apply(this_, args);
502 this_ = null;
503 args = null;
504 id = null;
505 }
506
507 return function () {
508 if (id !== null && this_ !== this)
509 _();
510 clearTimeout(id);
511 this_ = this;
512 args = arguments;
513 id = setTimeout(_, wait);
514 };
515 }
516
517 function pluck (prop, seq) {
518 return map(lookup(prop), seq);
519 }
520
521 Object.assign(this, {
522 allKeys: allKeys,
523 argcd: argcd,
524 argv: argv,
525 arrayify: arrayify,
526 clamp: clamp,
527 compose: compose,
528 construct: construct,
529 contains: contains,
530 counter: counter,
531 debounce: debounce,
532 each: each,
533 eachUntil: some,
534 eachr: eachr,
535 eachrUntil: eachrUntil,
536 every: every,
537 filter: filter,
538 foldl: foldl,
539 foldr: foldr,
540 getter: getter,
541 head: head,
542 I: I,
543 insertBefore: insertBefore,
544 ipairs: ipairs,
545 irange: irange,
546 isFunction: isFunction,
547 isString: isString,
548 K: K,
549 lacks: lacks,
550 last: last,
551 len: len,
552 lookup: lookup,
553 map: map,
554 mapr: mapr,
555 mapValues: mapValues,
556 max: max,
557 min: min,
558 most: most,
559 new_: new_,
560 none: none,
561 pluck: pluck,
562 range: range,
563 repeat: repeat,
564 seqEqual: seqEqual,
565 setter: setter,
566 slice: slice,
567 some: some,
568 stash: stash,
569 tail: tail,
570 transform: transform,
571 unbind: unbind,
572 volatile: volatile,
573 without: without,
574
575 VERSION: 0
576 });
577
578 }).call(typeof exports !== "undefined" ? exports : (this.yf = {}));