wu.js
wu.js
is a JavaScript library providing higher order
functions (such as map
, filter
,
and reduce
) for ECMAScript 6 iterators.
<script src="path/to/wu.js" defer></script>
<script src="https://raw.githubusercontent.com/mozilla/source-map/master/dist/wu.debug.js" defer></script>
or
<script src="https://raw.githubusercontent.com/mozilla/source-map/master/dist/wu.min.js" defer></script>
$ npm install wu
and
var wu = require("wu");
Note that this is the compiled-to-ES5 version.
Iterators represent a stream of data. To get the next value in the
stream, you call the iterator's next
method. This
enables both lazy and infinite sequences. Most
of the time you don't need to call next
yourself: when you use a for-of loop, you're using iterators
behind the scenes.
Anything can create iterators — they just need to make an
object with the proper next
interface — but
generator functions and the yield
expression provide
convenient syntactic sugar.
// Generate an infinite sequence of the fibonacci numbers.
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// Log each even fibonacci number that is less than ten.
const isEven = n => n % 2 == 0;
const lessThanTen = n => n < 10;
for (let n of fibs()) {
if (!lessThanTen(n)) {
break;
}
if (isEven(n)) {
console.log(n);
}
}
wu.js
provides the higher order functions you've come
to love from working with arrays (such
as map
and filter
) as well as ones
that may be new to JavaScript developers (such
as takeWhile
). With wu.js
,
we can rewrite the above example like this:
wu(fibs())
.filter(isEven)
.takeWhile(lessThanTen)
.forEach(console.log.bind(console));
The following is a simple immutable sorted set implementation that doesn't do tree balancing for simplicity. It has an iterator method which yields items in sorted order.
const theEmptySet = null;
function SortedSet(value, left=theEmptySet, right=theEmptySet) {
this.value = value;
this.left = left;
this.right = right;
}
SortedSet.prototype[wu.iteratorSymbol] = function* () {
if (this.left !== theEmptySet) {
yield* this.left;
}
yield this.value;
if (this.right !== theEmptySet) {
yield* this.right;
}
};
const insert = (set, x) => {
if (set === theEmptySet) {
return new SortedSet(x);
}
const { value, left, right } = set;
if (x < value) {
return new SortedSet(value, insert(left, x), right);
} else if (x > value) {
return new SortedSet(value, left, insert(right, x));
} else {
return set;
}
};
We can initialize a set of 100 random floats to work with:
let s = theEmptySet;
let n = 100;
while (n--) {
s = insert(s, Math.random());
}
To get the sum of all elements greater than .8 in the set:
wu(s).dropWhile(n => n <= .8).reduce((x, y) => x + y);
To find the number of elements that are less than .25:
wu(s).takeWhile(n => n < .25).length();
To find the first element whose square is greater than .5:
wu(s).filter(n => n * n > .5).find(n => n > .5);
wu(iterable).asyncEach(fn, maxBlock=wu.MAX_BLOCK, timeout=wu.TIMEOUT)
wu.asyncEach(fn, maxBlock, timeout, iterable)
curryableCall fn(item)
for each item in the (possibly infinite) iterable. Every
maxBlock
milliseconds, do a setTimeout
for timeout
milliseconds so that we
don’t hog the thread to ourselves. This gives the browser a chance to paint,
fire an event handler, or run another concurrent asyncEach
’s set of calls.
asyncEach
returns a Promise
that is resolved when iteration has completed.
Note: It is generally preferrable to use a Worker
instead of asyncEach
when
possible, as this will give you better throughput and responsiveness. However,
if you absolutely must do iteration over a very large number of items on the
main thread, asyncEach
will let you do it without getting a slow-script-dialog
for the tab.
wu.count().asyncEach(x => console.log(x));
// console.log: 0
// console.log: 1
// console.log: 2
// console.log: 3
// ...
wu.chain(...iterables)
Form a single iterator from consequtive iterables. Yields items from the first iterable until it is exhausted, then yields items from the second iterable until that one is exhausted, and so on until all elements from all iterables have been yielded.
wu.chain("ab", "cd", "ef")
// ("a", "b", "c", "d", "e", "f")
wu(iterable).chunk(n=2)
wu.chunk(n, iterable)
curryableAccumulate items from the iterable into arrays of size n
and yield each
array.
wu("abcdef").chunk(2);
// (["a", "b"], ["c", "d"], ["e", "f"])
wu("abcdef").chunk(3);
// (["a", "b", "c"], ["d", "e", "f"])
wu("abcdef").chunk(4);
// (["a", "b", "c", "d"], ["e", "f"])
wu(iterable).concatMap(fn)
wu.concatMap(fn, iterable)
curryableApplies the given function to each item in the iterable and yields each item from the result.
wu([1, 2, 3]).concatMap(x => [x, x * x])
// (1, 1, 2, 4, 3, 9)
wu.count(start=0, step=1)
Yield an infinite set of numbers starting with start
and incrementing by
step
.
wu.count()
// (0, 1, 2, 3, 4, 5, 6, ...)
wu.count(5)
// (5, 6, 7, 8, 9, 10, ...)
wu.count(0, 5)
// (0, 5, 10, 15, 20, 25, ...)
wu.curryable(fn, expected=fn.length)
Returns a new function that keeps currying until it receives expected
arguments, at which point it evaluates fn
with those arguments applied.
Learn more about currying at Wikipedia.
Most of the functions attached directly to wu
(eg wu.filter(fn, iterable)
,
as opposed to wu(iterable).filter(fn)
) are curryable.
You generally shouldn’t need to explicitly specify the number of arguments
expected unless you’re using rest parameters or optional parameters (which don’t
add increment the function’s length
property).
const add = wu.curryable((a, b) => a + b);
add(3, 4);
// 7
const add2 = add(2);
add2(10)
// 12
add()()()()()()()()();
// function
const sum = wu.reduce(add, 0);
sum([1,2,3,4,5]);
// 15
const hasProp = wu.curryable((prop, obj) => prop in obj);
const withAlias = wu.filter(hasProp("alias"));
const wantedDeadOrAlive = [
{ name: "Sammy Jones", alias: "Crime Time" },
{ name: "Jessica Carter", alias: "Sugar Killa" },
{ name: "Nick Fitzgerald" }
];
withAlias(wantedDeadOrAlive);
// ( { name: "Sammy Jones", alias: "Crime Time" },
// { name: "Jessica Carter", alias: "Sugar Killa" } )
wu(iterable).cycle()
wu.cycle(iterable)
curryableYield each item from the iterable and when the iterable is exhausted, start yielding its items all over again, and again, and again.
wu.cycle([1, 2, 3])
// (1, 2, 3, 1, 2, 3, 1, 2, 3, ...)
wu(iterable).drop(n)
wu.drop(n, iterable)
curryableDrop the first n
items from the iterable.
wu([5, 4, 3, 2, 1]).drop(2);
// (3, 2, 1)
wu(iterable).dropWhile(fn=Boolean)
wu.dropWhile(fn, iterable)
curryableDrop items from the iterable while the predicate is truthy.
wu([2, 4, 6, 5, 8, 10]).dropWhile(x => x % 2 === 0)
// (5, 8, 10)
wu.entries(object)
Yield [key, value]
pairs from the given object. Ordering of the pairs is
undefined and cannot be relied upon.
const obj = { foo: 1, bar: 2, baz: 3 };
wu.entries(obj);
// (["foo", 1], ["bar", 2], ["baz", 3])
wu(iterable).enumerate()
wu.enumerate(iterable)
curryableFor each item in the iterable, yield a pair [item, index]
.
wu.enumerate(["cats", "dogs", "rats", "hogs"]);
// (["cats", 0], ["dogs", 1], ["rats", 2], ["hogs", 3])
wu(iterable).every(fn=Boolean)
wu.every(fn, iterable)
curryableReturn true
if fn(item)
is truthy for every item in the iterable, otherwise
return false
.
wu([true, 36, "chambers"]).every();
// true
wu([true, false, true]).every();
// false
const allLessThan100 = wu.every(x => x < 100);
allLessThan100([1, 2, 3, 4, 5]);
// true
wu(iterable).filter(fn=Boolean)
wu.filter(fn, iterable)
curryableYield only the items from the iterable for which fn(item)
is truthy.
wu([false, true, false, true]).filter()
// (true, true)
wu([1, 2, 3, 4]).filter(x => x % 2 === 0)
// (2, 4)
wu(iterable).find(fn)
wu.find(fn, iterable)
curryableReturn the first item from the iterable for which fn(item)
is truthy. If no
item is found, undefined
is returned.
const myTeam = [
{ name: "Robert Fitzgerald Diggs", alias: "RZA" },
{ name: "Gary Grice", alias: "GZA" },
{ name: "Clifford Smith", alias: "Method Man" },
{ name: "Corey Woods", alias: "Raekwon" },
{ name: "Dennis Coles", alias: "Ghostface Killah" },
{ name: "Jason Hunter", alias: "Inspectah Deck" },
{ name: "Lamont Jody Hawkins", alias: "U-God" },
{ name: "Elgin Turner", alias: "Masta Killah" },
{ name: "Russell Tyrone Jones", alias: "ODB" }
];
wu(myTeam).find(({ name }) => name.contains("Fitzgerald"));
// { name: "Robert Fitzgerald Diggs", alias: "RZA" }
wu(iterable).flatten(shallow=false)
wu.flatten(shallow, iterable)
curryableFlatten the given iterable. If shallow
is truthy, only flatten by one level.
wu(["I", ["like", ["LISP"]]]).flatten()
// ("I", "like", "LISP")
wu.flatten(true, [1, [2], [3, [[4]]]])
// (1, 2, 3, [[4]]),
wu(iterable).forEach(fn)
wu.forEach(fn, iterable)
curryableCall fn(item)
for each item in the iterable.
Note that this can cause slow script dialogs or even permanently block the main
thread if used with large or infite iterators. In such cases, either use this
method inside a Worker
(preferrable) or use wu.asyncEach
.
wu.forEach(x => console.log("x is " + x),
[1, 2, 3]);
// console.log: "x is 1"
// console.log: "x is 2"
// console.log: "x is 3"
wu(iterable).has(thing)
wu.has(thing, iterable)
curryableReturns true
if thing
is in the iterable (using ===
comparison), otherwise
returns false
.
wu(["uno", "dos", "tres"]).has(1);
// false
wu.count().has(5);
// true
wu(iterable).invoke(methodName, ...args)
wu.invoke(methodName, ...args, iterable)
curryableFor each item in the iterable, yield item[methodName](...args)
.
wu([0,1,2,3,4]).invoke("toString", 2);
// ("0", "1", "10", "11", "100")
function Animal(type, noise) {
this.type = type;
this.noise = noise;
}
Animal.prototype.makeNoise = function () {
return this.type " says '" + this.noise + "'";
}
const animals = [
new Animal("cat", "meow"),
new Animal("dog", "woof"),
new Animal("rat", "squeek"),
new Animal("hog", "oink")
];
wu(animals).invoke("makeNoise");
// ("cat says 'meow'",
// "dog says 'woof'",
// "rat says 'squeek'",
// "hog says 'oink'")
wu.keys(object)
Yield the property name of each enumerable property on the object.
const obj = { uno: 1, dos: 2, tres: 3 };
wu.keys(obj);
// ("uno", "dos", "tres")
wu(iterable).map(fn)
wu.map(fn, iterable)
curryableApplies the given function to each item in the iterable and yields the result.
wu([1, 2, 3]).map(x => x * x);
// (1, 4, 9)
wu(iterable).nth(n)
wu.nth(n, iterable)
curryableReturn the nth item from the iterable. If n
is out of bounds, undefined
is returned.
wu([0, 1, 2, 3, 4]).nth(3);
// 3
wu(iterable).pluck(propertyName)
wu.pluck(propertyName, iterable)
curryableFor each item in the iterable, yield item[propertyName]
.
const myTeam = [
{ name: "Robert Fitzgerald Diggs", alias: "RZA" },
{ name: "Gary Grice", alias: "GZA" },
{ name: "Clifford Smith", alias: "Method Man" },
{ name: "Corey Woods", alias: "Raekwon" },
{ name: "Dennis Coles", alias: "Ghostface Killah" },
{ name: "Jason Hunter", alias: "Inspectah Deck" },
{ name: "Lamont Jody Hawkins", alias: "U-God" },
{ name: "Elgin Turner", alias: "Masta Killah" },
{ name: "Russell Tyrone Jones", alias: "ODB" }
];
wu(myTeam).pluck("alias");
// ("RZA", "GZA", "Method Man", ...)
wu(iterable).reduce(fn[, initial])
wu.reduce(fn, initial, iterable)
curryableReduce the iterable from left to right with the binary function fn
. If
initial
is supplied, start with that value, otherwise use the first value in
the iterable.
const plus = (x, y) => x + y;
wu([1,2,3,4,5]).reduce(plus);
// 15
wu.reduce(plus, 100, [1,2,3,4,5]);
// 115
wu(iterable).reductions(fn[, initial])
wu.reductions(fn, initial, iterable)
curryableSimilar to wu.reduce
but yields each intermediate reduction as the
iterable is reduced.
const multiply = (x, y) => x * y;
wu.count(1).reductions(multiply);
// (1, 2, 6, 24, 120, ...)
wu(iterable).reject(fn=Boolean)
wu.reject(fn, iterable)
curryableFor each item in the iterable, yield the item if !fn(item)
is truthy.
wu([false, true, false, true]).reject()
// (false, false)
wu([1, 2, 3, 4]).reject(x => x % 2 === 0)
// (1, 3)
wu.repeat(thing, n=Inifinity)
Create an iterable that yields thing
n
times.
wu.repeat(42)
// (42, 42, 42, 42, 42, ...)
wu.repeat("hello", 2)
// ("hello", "hello")
wu(iterable).slice(start=0, stop=Infinity)
wu.slice(start, stop, iterable)
curryableLike Array.prototype.slice
, but for any iterable.
wu.slice(start, end, iterable)
is equivalent to
wu(iterable).drop(start).take(end - start)
.
wu.count(10).slice(1, 4);
// (11, 12, 13)
wu(iterable).some(fn=Boolean)
wu.some(fn, iterable)
curryableReturn true
if fn(item)
is truthy for any of the items in the iterable,
otherwise return false
.
wu([false, false, true, false]).some();
// true
wu([1,2,3,4,5]).some(n => n > 10);
// false
wu(iterable).spreadMap(fn)
wu.spreadMap(fn, iterable)
curryableFor each item in the iterable, yield fn(...item)
.
const pairs = [
[2, 1],
[2, 2],
[2, 3],
[2, 4]
];
wu(pairs).spreadMap(Math.pow);
// (2, 4, 8, 16)
wu(iterable).take(n)
wu.take(n, iterable)
curryableYield the first n
items from the iterable.
wu.count().take(5);
// (0, 1, 2, 3, 4)
wu(iterable).takeWhile(fn=Boolean)
wu.takeWhile(fn, iterable)
curryableYield items from the iterable while fn(item)
is truthy.
wu([2, 4, 6, 5, 8]).takeWhile(n => n % 2 === 0);
// (2, 4, 6)
wu(["foo", "bar", null, "baz"]).takeWhile();
// ("foo", "bar")
wu(iterable).tap(fn=console.log.bind(console))
wu.tap(fn, iterable)
curryableFor each item in the iterable, call fn(item)
and then yield item
regardless
of the function’s returned value. This is useful for debugging chained methods.
const pairs = [
[2, 1],
[2, 2],
[2, 3],
[2, 4]
];
const log = msg => console.log.bind(console, msg);
const iter = wu(pairs)
.tap(log("initial: "))
.spreadMap(Math.pow)
.tap(log("after spreadMap: "))
.map(n => n + 1)
.tap(log("after + 1: "))
.reject(n => n < 7)
.tap(log("after reject: "));
iter.next().value;
// console.log: initial: [2, 1]
// console.log: after spreadMap: 2
// console.log: after + 1: 3
// console.log: initial: [2, 2]
// console.log: after spreadMap: 4
// console.log: after + 1: 5
// console.log: initial: [2, 3]
// console.log: after spreadMap: 8
// console.log: after + 1: 9
// console.log: after reject: 9
// 9
iter.next().value;
// console.log: initial: [2, 4]
// console.log: after spreadMap: 16
// console.log: after + 1: 17
// console.log: after reject: 17
// 17
wu(iterable).tee(n=2)
wu.tee(n, iterable)
curryableSplit the given iterable into n
duplicate iterators.
Warning: once you’ve split an iterator with tee
, you shouldn’t use the
original iterator again, or else the new iterators will get out of sync!
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const [fibs1, fibs2] = wu(fibs())
.tap(console.log.bind(console, "Calculated a fib:"))
.tee();
fibs1.next().value;
// console.log: Calculated a fib: 0
// 0
fibs1.next().value;
// console.log: Calculated a fib: 1
// 1
fibs1.next().value;
// console.log: Calculated a fib: 1
// 1
fibs1.next().value;
// console.log: Calculated a fib: 2
// 2
fibs1.next().value;
// console.log: Calculated a fib: 3
// 3
fibs1.next().value;
// console.log: Calculated a fib: 5
// 5
// Note that each value is only calculated once!
fibs2.next().value;
// 0
fibs2.next().value;
// 1
fibs2.next().value;
// 1
wu(iterable).toArray()
Converts an iterable to an Array.
wu.count().take(5).toArray();
// [0, 1, 2, 3, 4]
wu(iterable).unique()
wu.unique(iterable)
curryableFor each item in the iterable, yield only the first occurence of the item.
Note that all yielded items from the iterable are kept in a Set
, so memory
overhead may become significant while iterating over large collections.
wu([1, 2, 1, 1, 3, 2, 3]).unique();
// (1, 2, 3)
wu(iterable).unzip(n=2)
wu.unzip(n, iterable)
curryableGiven an iterable whose items are of the form [a, b, c, ...]
, return an array
of iterators of the form [as, bs, cs, ...]
.
const pairs = [
["one", 1],
["two", 2],
["three", 3]
];
const [i1, i2] = wu(pairs).unzip();
i1;
("one", "two", "three")
i2;
(1, 2, 3)
wu.values(object)
Yield the property value of each enumerable property on the object.
const obj = { uno: 1, dos: 2, tres: 3 };
wu.values(obj);
// (1, 2, 3)
wu.zip(...iterables)
Given n
iterables, yield the next item from each iterable as an array until
the shortest iterable is exhausted.
wu.zip("hello", [3, 2, 1]);
// (["h", 3], ["e", 2], ["l", 1])
wu.zipLongest(...iterables)
The same as wu.zip
, but keeps going until the longest iterable is
exhausted. When shorter iterables have been exhausted, undefined
is used in
place of their next items.
wu.zipLongest("hello", [3, 2, 1]);
// (["h", 3], ["e", 2], ["l", 1], ["l", undefined], ["o", undefined])
wu.zipWith(fn, ...iterables)
Given n
iterables, yield fn(itemFromIter1, itemFromIter2, ...,
itemFromIterN)
until the shortest iterable is exhausted. This is equivalent to
wu.zip(...iterables).spreadMap(fn)
.
wu.zipWith(Math.pow, wu.count(), wu.repeat(2));
// (0, 1, 4, 9, 16, 25, 36, 49, ...)
Development happens on GitHub. Include test(s) and documentation updates in your pull requests.
File bugs and feature requests in the GitHub issue tracker. When filing bugs, include:
To compile wu.js
and its tests to ES5 using the Babel compiler,
run:
$ npm run build
This command regenerates:
dist/wu.js
— The ES5 compatible version of wu.js
.
dist/wu.debug.js
— The ES5 compatible version of wu.js
with
an inline source map included.
dist/wu.min.js
— The minified ES5 compartible version of wu.js
.
$ npm test
To add a new test, find or create the appropriate file
in test/test-X.js
. Mocha
is the test
runner. Chai's assert
module is used for assertions.
This documentation is created with the static site generator Jekyll. To set up jekyll, run:
$ gem install jekyll bundler
$ cd path/to/wu.js
$ bundle install
Once Jekyll is set up, to serve the docs locally and automatically recompile them on change, run:
$ npm run docs
The documentation will be served at http://0.0.0.0:4000.
The sources for this documentation live in index.html
and markdown files in the _posts
directory. Each wu
method has its own markdown file
in the _posts
directory, and the table of contents
for thewu
methods is generated automatically. The CSS
styles live in index.css
.