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