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/
10 /** yf - yuu foundation
12 A collection of tools for functional programming and the kind
13 of sequence (array) manipulations usually associated with
14 functional programming.
16 yf doesn't depend on yuu specifically, but may use standard
17 features not yet widely supported provided by yuu/pre.
20 function isFunction (o
) {
21 /** Check if a value is a function */
22 return Object
.prototype.toString
.call(o
) === '[object Function]';
25 function isString (s
) {
26 /** Check if a value is a string */
27 return Object
.prototype.toString
.call(s
) === "[object String]";
30 function I (x
) { return x
; }
32 function K (x
) { return function () { return x
; }; }
34 /** Get the minimum or maximum of two objects
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.
42 These functions work on any objects comparable with < and >,
43 and only consider two arguments.
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
)); }
49 /** Get a property from the this object by name */
50 function getter (key
) { return this[key
]; }
52 /** Set a property value on the this object by name */
53 function setter (key
, value
) { this[key
] = value
; }
56 /** Factor out the special 'this' argument from a function
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.
63 // return f.call.bind(f) is ~15% slower than this closure on
64 // Chrome 31 and negligibly slower on Firefox 25.
66 return f
.call
.apply(f
, arguments
);
70 /** Unbound versions of useful functions */
71 var slice
= unbind(Array
.prototype.slice
);
73 function contains (seq
, o
) {
74 return Array
.prototype.indexOf
.call(seq
, o
) !== -1;
77 function lacks (seq
, o
) {
78 return !contains(seq
, o
);
81 /** The first element of a sequence */
82 function head (a
) { return a
[0]; }
84 /** All but the first element of a sequence */
85 function tail (a
) { return slice(a
, 1); }
87 /** The last element of a sequence */
88 function last (a
) { return a
[a
.length
- 1]; }
90 /** All but the last element of a sequence */
91 function most (a
) { return slice(a
, 0, -1); }
93 /** The length of a sequence */
94 function len (seq
) { return seq
.length
; }
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).
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.
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)
109 yf.each.call(thisArg, callback, someArray)
112 function foldl (callback
, seq
, value
) {
113 /** Like Array's reduce, but with no extra arguments
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.
120 if (arguments
.length
=== 2)
122 for (; i
< seq
.length
; ++i
)
123 value
= callback
.call(this, value
, seq
[i
]);
127 function foldr (callback
, seq
, value
) {
128 /** Like foldl, but reversed.
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.
134 var i
= seq
.length
- 1;
135 if (arguments
.length
=== 2)
138 value
= callback
.call(this, seq
[i
], value
);
142 function call (f
, x
) { return f
.call(this, x
); }
144 function compose () {
145 /** Create a function by composing multiple functions
147 compose(f, g, h)(x, y, z) is equivalent to f(g(h(x, y, z))).
149 var inner
= last(arguments
);
150 var funcs
= most(arguments
);
152 return foldr
.call(this, call
, funcs
, inner
.apply(this, arguments
));
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.
160 // So generate specialized functions for small argc, and only
161 // resort to the generic approach when there's many sequences.
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("") + ");");
171 function lookup (prop
) {
172 /** Return a function that gets a particular property */
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
]; };
179 function callx (callback
, i
, seqs
) {
180 return callback
.apply(this, seqs
.map(lookup(i
)));
183 function calln (argc
) {
184 return CALLS
[argc
] || callx
;
188 /** Provide a variadic-like version of f
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.
194 var length
= f
.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
); };
200 var args
= slice(arguments
, 0, length
);
201 args
[length
- 1] = slice(arguments
, length
- 1);
202 return f
.apply(this, args
);
207 var each
= argv(function (callback
, seqs
) {
208 /** Call a function on the given sequences' elements
210 If one sequence is longer than the others, `undefined`
211 will be passed for that sequence's argument.
213 For example, each(f, [a, b, c], [x, y]) is equivalent to
218 If you want to pass a thisArg to the callback, use
219 each.call(thisArg, callback, ...).
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
);
227 var eachr
= argv(function (callback
, seqs
) {
228 /** Call a function on the given sequences' elements, backwards
230 If one sequence is longer than the others, `undefined` will be
231 passed for that sequence's argument.
233 For example, each(f, [a, b, c], [x, y]) is equivalent to
238 If you want to pass a thisArg to the callback, use
239 eachr.call(thisArg, callback, ...).
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
);
247 function pusher (a
) {
248 // Ridiculously faster than a.push.bind(a). :(
249 return function (o
) { a
.push(o
); };
253 /** Build an array by applying a function to sequences
255 Similar to Array.prototype.map, but allows passing multiple
256 sequences to call functions of any arity, as with each.
258 If you want to pass a thisArg to the callback, use
259 map.call(thisArg, callback, ...).
262 var args
= slice(arguments
);
263 args
[0] = compose(pusher(a
), args
[0]);
264 each
.apply(this, args
);
269 /** Build an array by applying a function to sequences backwards
271 As eachr is to each, so mapr is to map.
274 var args
= slice(arguments
);
275 args
[0] = compose(pusher(a
), args
[0]);
276 eachr
.apply(this, args
);
280 function filter (callback
, seq
) {
281 /** Build an array without the elements that fail the predicate
283 Like Array.prototype.filter, but if null is passed for the
284 predicate, any false-y values will be removed.
287 callback
= callback
|| I
;
288 function _ (o
) { if (callback
.call(this, o
)) a
.push(o
); }
289 each
.call(this, _
, seq
);
293 function packed (f
) {
294 /** Pack arguments to a function into an Array
296 packed(f)([a, b, c]) is equivalent to f(a, b, c).
298 return function (args
) { return f
.apply(this, args
); };
302 /** Create a function that dispatches based on argument count
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) { ... });
309 This can be used for function overloading, clearer
310 defaults for positional parameters, or significantly more
313 The functions can also be accessed directly by their
314 argument count, e.g. f[0], f[2], f[3] above.
316 If no matching argument count is found for a call, a
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
);
324 for (var i
= 0; i
< arguments
.length
; ++i
)
325 table
[arguments
[i
].length
] = arguments
[i
];
330 /** Call the provided callback counting as it goes
332 irange(x) counts from [0, x), by 1.
334 irange(x, y) counts from [x, y), by 1.
336 irange(x, y, s) counts from [x, y), by s.
338 function (callback
, stop
) {
339 return irange
.call(this, callback
, 0, stop
, 1);
341 function (callback
, start
, stop
) {
342 return irange
.call(this, callback
, start
, stop
, 1);
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
);
351 function capture1 (ifunc
) {
352 /** Build an array from the values of an iterating function
354 The elements added to the array are the first arguments
355 the iterating function passes.
359 var args
= slice(arguments
);
360 args
.unshift(pusher(a
));
361 ifunc
.apply(this, args
);
366 /** Generate an Array from range of numbers, as irange */
367 var range
= capture1(irange
);
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
]); },
375 function iprototypeChain (callback
, o
) {
376 /** Traverse an object and its prototype chain */
378 callback
.call(this, o
);
379 } while ((o
= Object
.getPrototypeOf(o
)));
382 function allKeys (o
) {
383 /** Get *ALL* property names. Even unowned non-enumerable ones. */
385 iprototypeChain(compose(packed(props
.push
).bind(props
),
386 Object
.getOwnPropertyNames
),
388 return props
.sort().filter(function (p
, i
, a
) {
389 return p
!== a
[i
- 1];
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
))
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
))
414 /** Build a function equivalent to !f(...) */
415 return function () { return !f
.apply(this, arguments
); };
419 var args
= slice(arguments
);
420 args
[0] = not(args
[0] || I
);
421 return !some
.apply(this, args
);
425 return !some
.apply(this, arguments
);
428 function repeat (o
, n
) {
429 return (new Array(n
)).fill(o
);
432 function arrayify (o
) {
433 /** Equivalent to [].concat(o) but faster */
434 return Array
.isArray(o
) ? o
: [o
];
437 function stash (key
, value
, a
) {
438 each(function (o
) { o
[key
] = value
; }, a
);
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
);
448 function construct (ctr
, args
) {
451 return new (Function
.prototype.bind
.apply(ctr
, args
))();
454 function new_ (ctr
) {
455 return function () { return construct(ctr
, arguments
); };
458 function counter (start
) {
459 var i
= +(start
|| 0);
460 return function () { return i
++; };
463 function volatile (f
) {
464 return { valueOf
: f
};
467 function transform (callback
, seq
) {
468 irange
.call(this, function (i
) {
469 seq
[i
] = callback
.call(this, seq
[i
]);
473 function mapValues (callback
, o
) {
476 r
[k
] = callback
.call(this, o
[k
]);
480 function insertBefore (seq
, o
, before
) {
481 var idx
= seq
.lastIndexOf(before
);
485 seq
.splice(idx
, 0, o
);
489 function seqEqual (a
, b
) {
490 if (a
.length
!== b
.length
)
492 for (var i
= 0; i
< a
.length
; ++i
)
498 function debounce (callback
, wait
) {
505 callback
.apply(this_
, args
);
512 if (id
!== null && this_
!== this)
517 id
= setTimeout(_
, wait
);
521 function pluck (prop
, seq
) {
522 return map(lookup(prop
), seq
);
525 Object
.assign(this, {
532 construct
: construct
,
539 eachrUntil
: eachrUntil
,
547 insertBefore
: insertBefore
,
550 isFunction
: isFunction
,
559 mapValues
: mapValues
,
574 transform
: transform
,
582 }).call(typeof exports
!== "undefined" ? exports
: (this.yf
= {}));