0779e7e25268248ec80acb8322b8b83b11a75623
[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 var args = slice(arguments, 0, length - 1);
201 args.push(slice(arguments, length - 1));
202 return f.apply(this, args);
203 };
204 }
205 }
206
207 var each = argv(function (callback, seqs) {
208 /** Call a function on the given sequences' elements
209
210 If one sequence is longer than the others, `undefined`
211 will be passed for that sequence's argument.
212
213 For example, each(f, [a, b, c], [x, y]) is equivalent to
214 f(a, x);
215 f(b, y);
216 f(c, undefined);
217
218 If you want to pass a thisArg to the callback, use
219 each.call(thisArg, callback, ...).
220 */
221 var length = Math.max.apply(Math, seqs.map(len));
222 var fn = calln(seqs.length);
223 for (var i = 0; i < length; ++i)
224 fn.call(this, callback, i, seqs);
225 });
226
227 var eachr = argv(function (callback, seqs) {
228 /** Call a function on the given sequences' elements, backwards
229
230 If one sequence is longer than the others, `undefined` will be
231 passed for that sequence's argument.
232
233 For example, each(f, [a, b, c], [x, y]) is equivalent to
234 f(c, undefined);
235 f(b, y);
236 f(a, x);
237
238 If you want to pass a thisArg to the callback, use
239 eachr.call(thisArg, callback, ...).
240 */
241 var length = Math.max.apply(Math, seqs.map(len));
242 var fn = calln(seqs.length);
243 for (var i = length - 1; i >= 0; --i)
244 fn.call(this, callback, i, seqs);
245 });
246
247 function pusher (a) {
248 // Ridiculously faster than a.push.bind(a). :(
249 return function (o) { a.push(o); };
250 }
251
252 function map () {
253 /** Build an array by applying a function to sequences
254
255 Similar to Array.prototype.map, but allows passing multiple
256 sequences to call functions of any arity, as with each.
257
258 If you want to pass a thisArg to the callback, use
259 map.call(thisArg, callback, ...).
260 */
261 var a = [];
262 arguments[0] = compose(pusher(a), arguments[0]);
263 each.apply(this, arguments);
264 return a;
265 }
266
267 function mapr () {
268 /** Build an array by applying a function to sequences backwards
269
270 As eachr is to each, so mapr is to map.
271 */
272 var a = [];
273 arguments[0] = compose(pusher(a), arguments[0]);
274 eachr.apply(this, arguments);
275 return a;
276 }
277
278 function filter (callback, seq) {
279 /** Build an array without the elements that fail the predicate
280
281 Like Array.prototype.filter, but if null is passed for the
282 predicate, any false-y values will be removed.
283 */
284 var a = [];
285 callback = callback || I;
286 function _ (o) { if (callback.call(this, o)) a.push(o); }
287 each.call(this, _, seq);
288 return a;
289 }
290
291 function packed (f) {
292 /** Pack arguments to a function into an Array
293
294 packed(f)([a, b, c]) is equivalent to f(a, b, c).
295 */
296 return function (args) { return f.apply(this, args); };
297 }
298
299 function argcd () {
300 /** Create a function that dispatches based on argument count
301
302 Pass a list of functions with varying argument lengths, e.g.
303 var f = argcd(function () { ... },
304 function (a, b) { ... },
305 function (a, b, c) { ... });
306
307 This can be used for function overloading, clearer
308 defaults for positional parameters, or significantly more
309 evil things.
310
311 The functions can also be accessed directly by their
312 argument count, e.g. f[0], f[2], f[3] above.
313
314 If no matching argument count is found for a call, a
315 TypeError is raised.
316 */
317 if (arguments.length === 1)
318 throw new Error("don't use argcd for one function");
319 var table = (function table () {
320 return table[arguments.length].apply(this, arguments);
321 });
322 for (var i = 0; i < arguments.length; ++i)
323 table[arguments[i].length] = arguments[i];
324 return table;
325 }
326
327 var irange = argcd(
328 /** Call the provided callback counting as it goes
329
330 irange(x) counts from [0, x), by 1.
331
332 irange(x, y) counts from [x, y), by 1.
333
334 irange(x, y, s) counts from [x, y), by s.
335 */
336 function (callback, stop) {
337 return irange.call(this, callback, 0, stop, 1);
338 },
339 function (callback, start, stop) {
340 return irange.call(this, callback, start, stop, 1);
341 },
342 function (callback, start, stop, step) {
343 var length = Math.max(Math.ceil((stop - start) / step), 0);
344 for (var i = 0; i < length; ++i)
345 callback.call(this, start + step * i);
346 }
347 );
348
349 function capture1 (ifunc) {
350 /** Build an array from the values of an iterating function
351
352 The elements added to the array are the first arguments
353 the iterating function passes.
354 */
355 return function () {
356 var a = [];
357 var args = slice(arguments);
358 args.unshift(pusher(a));
359 ifunc.apply(this, args);
360 return a;
361 };
362 }
363
364 /** Generate an Array from range of numbers, as irange */
365 var range = capture1(irange);
366
367 function ipairs (callback, o) {
368 /** Call the provided callback with each key and value of the object */
369 each.call(this, function (k) { callback.call(this, k, o[k]); },
370 Object.keys(o));
371 }
372
373 function iprototypeChain (callback, o) {
374 /** Traverse an object and its prototype chain */
375 do {
376 callback.call(this, o);
377 } while ((o = Object.getPrototypeOf(o)));
378 }
379
380 function allKeys (o) {
381 /** Get *ALL* property names. Even unowned non-enumerable ones. */
382 var props = [];
383 iprototypeChain(compose(packed(props.push).bind(props),
384 Object.getOwnPropertyNames),
385 o);
386 return props.sort().filter(function (p, i, a) {
387 return p !== a[i - 1];
388 });
389 }
390
391 var some = argv(function (callback, seqs) {
392 callback = callback || I;
393 var length = Math.max.apply(Math, seqs.map(len));
394 var fn = calln(seqs.length);
395 for (var i = 0; i < length; ++i)
396 if (fn.call(this, callback, i, seqs))
397 return true;
398 return false;
399 });
400
401 var eachrUntil = argv(function (callback, seqs) { // Reverse of `some`
402 callback = callback || I;
403 var length = Math.max.apply(Math, seqs.map(len));
404 var fn = calln(seqs.length);
405 for (var i = length - 1; i >= 0; --i)
406 if (fn.call(this, callback, i, seqs))
407 return true;
408 return false;
409 });
410
411 function not (f) {
412 /** Build a function equivalent to !f(...) */
413 return function () { return !f.apply(this, arguments); };
414 }
415
416 function every () {
417 arguments[0] = not(arguments[0] || I);
418 return !some.apply(this, arguments);
419 }
420
421 function none () {
422 return !some.apply(this, arguments);
423 }
424
425 function repeat (o, n) {
426 return (new Array(n)).fill(o);
427 }
428
429 function arrayify (o) {
430 /** Equivalent to [].concat(o) but faster */
431 return Array.isArray(o) ? o : [o];
432 }
433
434 function stash (key, value, a) {
435 each(function (o) { o[key] = value; }, a);
436 }
437
438 function without (seq, o) {
439 if (arguments.length === 2)
440 return filter(function (a) { return a !== o; }, seq);
441 var os = slice(arguments, 1);
442 return filter(lacks.bind(null, os), seq);
443 }
444
445 function construct (ctr, args) {
446 args = slice(args);
447 args.unshift(ctr);
448 return new (Function.prototype.bind.apply(ctr, args))();
449 }
450
451 function new_ (ctr) {
452 return function () { return construct(ctr, arguments); };
453 }
454
455 function counter (start) {
456 var i = +(start || 0);
457 return function () { return i++; };
458 }
459
460 function volatile (f) {
461 return { valueOf: f };
462 }
463
464 function transform (callback, seq) {
465 irange.call(this, function (i) {
466 seq[i] = callback.call(this, seq[i]);
467 }, seq.length);
468 }
469
470 function mapValues (callback, o) {
471 var r = {};
472 for (var k in o)
473 r[k] = callback.call(this, o[k]);
474 return r;
475 }
476
477 function insertBefore (seq, o, before) {
478 var idx = seq.lastIndexOf(before);
479 if (idx === -1)
480 seq.push(o);
481 else
482 seq.splice(idx, 0, o);
483 return seq;
484 }
485
486 function seqEqual (a, b) {
487 if (a.length !== b.length)
488 return false;
489 for (var i = 0; i < a.length; ++i)
490 if (a[i] !== b[i])
491 return false;
492 return true;
493 }
494
495 function debounce (callback, wait) {
496 wait = wait || 100;
497 var this_ = null;
498 var args = null;
499 var id = null;
500
501 function _ () {
502 callback.apply(this_, args);
503 this_ = null;
504 args = null;
505 id = null;
506 }
507
508 return function () {
509 if (id !== null && this_ !== this)
510 _();
511 clearTimeout(id);
512 this_ = this;
513 args = arguments;
514 id = setTimeout(_, wait);
515 };
516 }
517
518 function pluck (prop, seq) {
519 return map(lookup(prop), seq);
520 }
521
522 Object.assign(this, {
523 allKeys: allKeys,
524 argcd: argcd,
525 argv: argv,
526 arrayify: arrayify,
527 clamp: clamp,
528 compose: compose,
529 construct: construct,
530 contains: contains,
531 counter: counter,
532 debounce: debounce,
533 each: each,
534 eachUntil: some,
535 eachr: eachr,
536 eachrUntil: eachrUntil,
537 every: every,
538 filter: filter,
539 foldl: foldl,
540 foldr: foldr,
541 getter: getter,
542 head: head,
543 I: I,
544 insertBefore: insertBefore,
545 ipairs: ipairs,
546 irange: irange,
547 isFunction: isFunction,
548 isString: isString,
549 K: K,
550 lacks: lacks,
551 last: last,
552 len: len,
553 lookup: lookup,
554 map: map,
555 mapr: mapr,
556 mapValues: mapValues,
557 max: max,
558 min: min,
559 most: most,
560 new_: new_,
561 none: none,
562 pluck: pluck,
563 range: range,
564 repeat: repeat,
565 seqEqual: seqEqual,
566 setter: setter,
567 slice: slice,
568 some: some,
569 stash: stash,
570 tail: tail,
571 transform: transform,
572 unbind: unbind,
573 volatile: volatile,
574 without: without,
575
576 VERSION: 0
577 });
578
579 }).call(typeof exports !== "undefined" ? exports : (this.yf = {}));