Introduction
What is wu.js?
Wu.js is a library for lazy, functional programming in
Javascript. Works great in the browser, and also with CommonJS
(including node and Narwhal).
The largest part of wu is dedicated to iterators. Iterators are lazy sequences with a number of methods that encourage functional programming. What does it mean to be lazy? Many people might expect the following code to log "1 squared is 1", "2 squared is 4", and "3 squared is 9" immediately.
wu([1,2,3]).map(function (n) {
console.log(n + " squared is " + (n*n));
return n*n;
});
However, nothing is logged. Because iterators are lazy, you must explicitly ask for an item and force evaluation. As you ask for items from this iterator, the messages will come one by one.
>>> var iterator = wu([1,2,3]).map(function (n) {
... console.log(n + " squared is " + (n*n));
... return n*n;
... });
>>> iterator.next();
"1 squared is 1"
1
>>> iterator.next();
"2 squared is 4"
4
Calling iterator.next() directly isn't very fun though, so there
are a few methods that make this logic cleaner for you.
The other large chunk of wu.js is a set of higher order
functions that strongly encourage a functional style. Examples include
Haskell-esque pattern matching with wu.match, function
composition, and partial application.
How to read this document
By following along, of course! This page has wu.js
included just so that you can open up your console, tweak every
example, and play to your heart's content.
Inspirations
In no particular order:
- Common Lisp
- Haskell
- itertools
- jQuery
- Prototype
- underscore.js
- Functional.js
- match-js
- closure/goog/iter
Download
API
Iterators
Lazy
wu.chain
wu.chain(iterable1, iterable2, ..., iterableN)
Chain the iterable arguments to produce a new iterator over all items in the iterables.
>>> wu.chain([1,2], [3,4], [5,6]).toArray(); [1, 2, 3, 4, 5, 6]
wu.cycle
wu.cycle(iterable)
Create an infinite sequence yielding items from the iterable. Once
the iterable is exhausted, begin yielding from the start again.
Note: trying to force evaluation on infinite sequences will send your
code in to an infinite loop and freeze the browser!
Use .takeWhile(), .asyncEach(),
or .next() when working with infinite sequences.
>>> var iter = wu.cycle([1,2,3]); >>> iter.next(); 1 >>> iter.next(); 2 >>> iter.next(); 3 >>> iter.next(); 1 >>> iter.next(); 2 >>> iter.next(); 3 >>> iter.next(); 1
wu.range
wu.range(stop)
wu.range(start, stop)
wu.range(start, stop, incr)
Returns an iterator that yields integers from start up to
(but not including) stop, incrementing
by incr. Start defaults to
0, incr defaults to 1.
>>> wu.range(3).toArray(); [0, 1, 2] >>> wu.range(3, 6).toArray() [3, 4, 5] >>> wu.range(0, 7, 2).toArray() [0, 2, 4, 6]
wu.zip
wu.zip(iterable1, iterable2, ..., iterableN)
Returns an iterator that yields an array of items [item1, item2,
..., itemN], where each item is the next item in the
corresponding iterable. Iterables can be Iterators, or anything that
can be coerced to an iterator.
>>> wu.zip(wu.cycle(["even", "odd"]), wu.range(6)).toArray(); [["even", 0], ["odd", 1], ["even", 2], ["odd", 3], ["even", 4], ["odd", 5]] >>> wu.zip([1,2], [3,4], [5,6], [7,8]).next(); [1, 3, 5, 7]
wu.zipWith
wu.zipWith(fn, iterable1, iterable2, ..., iterableN)
Returns an iterator that yields the result of applying fn
to the items at the same index in every iterable. Iterables can be Iterators,
or anything that can be coerced to an iterator.
>>> var add2 = function (a, b) { return a + b; };
>>> wu.zipWith(add2, [1,2,3], [4,5,6]).toArray();
[5, 7, 9]
>>> var add3 = function (a, b, c) { return a + b + c; };
>>> wu.zipWith(add3, [1, 2, 3], [4, 5, 6], [7, 8, 9]).toArray();
[12, 15, 18]
wu.Iterator.dot
iterator.dot(propertyName)
iterator.dot(methodName)
iterator.dot(methodName, arg1, arg2, ..., argN)
Access a property or call a method of each item in this iterator.
>>> wu({ foo: 1}, { foo: 2 }, { foo: 3}).dot("foo").toArray();
[1, 2, 3]
>>> wu([1, "hi", {}]).dot("toString").toArray();
["1", "hi", "[object Object]"]
>>> wu([[1], [2,3], [4,5,6]]).dot("slice", 1).toArray();
[[], [3], [5,6]]
wu.Iterator.dropWhile
iterator.dropWhile(testFn)
iterator.dropWhile(testFn, context)
Drop items from this iterator while testFn returns a
truthy value. After it returns a non-truthy value, yield each
remaining element in turn. Context will be used
as this inside of the testFn. It defaults to
the iterator.
>>> wu([1,2,3,2,1]).dropWhile(function (n) { return n < 3; }).toArray();
[3,2,1]
wu.Iterator.filter
iterator.filter(testFn)
iterator.filter(testFn, context)
Returns an iterator that yields only the items where
testFn.call(context, item) returns
truthy. Context defaults to the iterator.
>>> wu([1,2,3,4]).filter(function (x) { return x % 2 === 0; }).toArray();
[2, 4]
wu.Iterator.has
iterator.has(element)
Is element inside of this iterator? Equality is
calculated with wu.eq.
>>> wu([0,1,2]).has(3);
false
>>> wu([{foo:1}, {bar:2}, {baz:3}]).has({bar:2});
true
wu.Iterator.map
iterator.map(fn)
iterator.map(fn, context)
Returns an iterator that yields fn.call(context, item)
for every item in this iterator. Context defaults to this
iterator.
>>> wu.range(5).map(function (n) { return n * n; }).toArray();
[0, 1, 4, 9, 16]
>>> wu.range(10).map(Math.sqrt).map(Math.ceil).toArray();
[0, 1, 2, 2, 2, 3, 3, 3, 3, 3]
wu.Iterator.mapply
iterator.mapply(fn)
iterator.mapply(fn, context)
The same as wu.Iterator.map, except instead of yielding
fn.call(context, item), it is assumed that every item is
an array and the resulting iterator yields fn.apply(context,
item) for every item in this iterator. Context
defaults to this iterator.
Mapply is especially useful with iterating over objects,
since iterators yield [key, value] pairs for
objects. However, remember that you can't expect there to be any
particular order when iterating over objects.
>>> wu([[0,2], [1,2], [2,2], [3,2]]).mapply(Math.pow).toArray();
[0, 1, 4, 9]
>>> wu({foo:1, bar:2, baz:3}).mapply(function (k, v) { return k + " is " + v; })
... .toArray()
... .join(", ");
"foo is 1, bar is 2, baz is 3"
wu.Iterator.next
iterator.next()
The next method is the low level building block
of wu.Iterator. Calling it will return the next item in
this iterator. If the iterator is exhausted and there are no more
items in this iterator, the wu.StopIteration error is
thrown. Generally, you shouldn't need to access this method directly
since there are so many other methods like each
and toArray that would probably better fit your needs.
>>> var iterator = wu({ foo: 1, bar: 2 });
>>> iterator.next();
["foo", 1]
>>> iterator.next();
["bar", 2]
>>> iterator.next();
StopIteration
>>> iterator = wu("Hi");
>>> iterator.next();
"H"
>>> iterator.next();
"i"
>>> iterator.next();
StopIteration
>>> iterator = wu([1,2]);
>>> iterator.next();
1
>>> iterator.next();
2
>>> iterator.next();
StopIteration
wu.Iterator.takeWhile
iterator.takeWhile(testFn)
iterator.takeWhile(fn, context)
Returns a new iterator that yields elements from this iterator as long
as testFn.call(context, item) is
truthy. Context defaults to this iterator. Useful when
working with infinite sequences.
>>> var i = 10;
>>> wu.cycle([1,2,3]).takeWhile(function (n) { return i-- !== 0; }).toArray();
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
Forceful
All of the following methods will force the iterator to be
evaluated. Beware: these methods will attempt to completely evaluate
infinite sequences which will send the code in to an infinite loop and
make the browser freeze! Use wu.Iterator.asyncEach on
infinite sequences or make them finite
via wu.Iterator.takeWhile.
wu.Iterator.all
iterator.all()
iterator.all(testFn)
iterator.all(testFn, context)
Returns true if testFn.call(context, item) is truthy for all items in
this iterator. Context defaults to this iterator,
and testFn defualts to coercion to boolean.
>>> wu([true, 1, {}]).all();
true
>>> wu([true, 0, null]).all();
false
>>> wu([1,2,3,4]).all(function (n) { return n < 5; });
true
wu.Iterator.any
iterator.any()
iterator.any(testFn)
iterator.any(testFn, context)
Returns true if testFn.call(context, item) is truthy for any items in
this iterator. Context defaults to this iterator,
and testFn defaults to coercion to boolean.
>>> wu([false, false, 1, false]).any();
true
>>> wu([false, 0, null, undefined]).any();
false
>>> wu([1,2,3,4,5]).any(function (n) { return n % 2 === 0; })
true
wu.Iterator.asyncEach
iterator.asyncEach(fn)
iterator.asyncEach(fn, then)
iterator.asyncEach(fn, then, context)
Asynchronously call fn.call(context, item) for every item
in this iterator. Order is maintained. Calls the then
callback once (and if) iteration is finished. Context
defaults to this iterator.
This is the only safe way to force evaluation of infinite
sequences without freezing the browser. It does this by
yielding control to the UI thread between every iteration via
setTimeout.
>>> wu(Infinity).asyncEach(wu.curry(wu.bind(console, console.log), ... "All work and no play makes Jack a dull boy.")); "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." "All work and no play makes Jack a dull boy." . . .
wu.Iterator.each
iterator.each(fn)
iterator.each(fn, context)
Runs fn.call(context, item) until all items from the
iterator are exhausted. Returns the collection of all items from this
iterator as an array. Context defaults to this iterator.
>>> wu(document.querySelectorAll("h2")).each(function (el) {
... el.parentElement.removeChild(el);
... });
[<h2>, <h2>, <h2>, ...]
wu.Iterator.eachply
iterator.eachply(fn)
iterator.eachply(fn, context)
Runs fn.apply(context, item) until all items from the
iterator are exhausted. Returns the collection of all items from this
iterator as an array. Context defaults to this iterator.
Useful for use with objects, since iterators yield [key,
value] pairs from objects. However, keep in mind that order is
not guaranteed when iterating over objects.
>>> var userRecord = {
... name:"Johnny Keyboard",
... username: "keyboaj",
... password:"nunya"
... };
>>> wu(userRecord).eachply(function (k, v) {
... $("ul:last").append("<li>" + k + ": " + v + "</li>");
... });
[["name", "Johnny Keyboard"], ["username", "keyboaj"], ["password", "nunya"]]
wu.Iterator.force
A synonym for wu.Iterator.toArray().
wu.Iterator.groupBy
iterator.groupBy(propertyName)
iterator.groupBy(methodName)
iterator.groupBy(methodName, arg1, arg2, ..., argN)
Aggregate each item in this iterator based on
common item.propertyName or item.methodName(arg1,
arg2, ..., argN) values.
>>> var people = [{first:"Nick", last:"Fitzgerald"},
... {first:"Nick", last:"Nolte"},
... {first:"John", last:"Smith"}];
>>> wu(people).groupBy("first");
{ Nick: [{first:"Nick", last:"Fitzgerald"},
{first:"Nick", last:"Nolte"}],
John: [{first:"John", last:"Smith"}] }
wu.Iterator.reduce
iterator.reduce(fn)
iterator.reduce(fn, initial)
iterator.reduce(fn, initial, context)
Applies fn against each item in this iterator, left to
right, building them up to accumulate a single
value. Initial defaults to the first item in this
iterator, context defaults to this iterator.
>>> wu([1,2,3,4]).reduce(function (n, m) { return n + m; });
10
wu.Iterator.reduceRight
iterator.reduceRight(fn)
iterator.reduceRight(fn, initial)
iterator.reduceRight(fn, initial, context)
The exact same as wu.Iterator.reduce, except that instead
of accumulating from left to right, it accumulates from right to
left. Initial defaults to the last item in this
iterator, context defaults to this iterator.
>>> wu([[1,2,3], [4,5], [6,7,8]]).reduceRight(function (a, b) {
... return a.concat(b);
... });
[1, 2, 3, 4, 5, 6, 7, 8]
wu.Iterator.toArray
iterator.toArray()
Force evaluation and return every item in this iterator as an array.
>>> wu.range(1, 5).map(Math.log).toArray();
[0, 0.6931471805599453, 1.0986122886681098, 1.3862943611198906]
>>> wu({foo:1, bar:2}).toArray();
[["foo",1], ["bar", 2]]
Extending iterators
wu.fn
To extend wu.Iterator and add your own methods, simply
attach your custom method to wu.fn. Here is an example
that allows you to filter and map at the same time, with the same
function. Any null value is assumed to be unwanted, and
is filtered.
>>> wu.fn.mapFilter = function mapFilter(fn, context) {
... context = context || this;
... return this.map(fn, context).filter(function (obj) {
... return obj !== null;
... });
... }
wu.Iterator(function next() { ... })
If your custom method is more complex, and you want to just implement
the iterators .next() method directly, there is a way to
do that too. Pass your .next() method to the wu.Iterator
constructor directly. Here is the implementation
of wu.Iterator.dropWhile which uses this technique (modified for readability).
// While fn.call(context, item) is "truthy", do not return any items from
// this iterable.
wu.fn.dropWhile = function dropWhile(fn, context) {
var keepDropping = true, that = this;
context = context || this;
return wu.Iterator(function next() {
var item = that.next();
return keepDropping && fn.call(context, item) ?
next() :
(function () {
keepDropping = false;
return item;
}());
});
};
Marking iterators empty with wu.fn.stop
When writing you custom iterator method, if you want to make the
iterator stop yielding items earlier than it normally would, just
call wu.fn.stop (which is available
as this.stop() from within your next
method). It will do the following in order:
-
Replace this iterator instance's
.next()method withfunction () { throw new StopIteration(); }so that the remaining items in your iterator are no longer accessible. -
Throw a
StopIterationerror to signify that this iterator is empty.
Pattern matching
wu.match
wu.match(pattern1, then1, pattern2, then2, ..., patternN, thenN)
Returns a function that performs Erlang-/Haskell-esque pattern
matching on the arguments object when it is called. To match on the
type of an object, use its constructor as a placeholder. To provide a
pattern that will always succeed, use wu.___ as the
placeholder. If no match is found, a TypeError is thrown.
Below is a simple example involving matching arguments to numbers
literals and the Number constructor. Notice
the TypeError when no match is found.
>>> var describeNums = wu.match([ 1 ], "One!",
... [ 2 ], "Two!",
... [ Number ], "Some other number!")
>>> describeNums(1);
"One!"
>>> describeNums(2);
"Two!"
>>> describeNums(3);
"Some other number!"
>>> describeNums({});
TypeError: wu.match: The form did not match any given pattern.
Here is an example showcasing the typical definition of factorial.
>>> var factorial = wu.match([ 0 ], 1,
... [ Number ], function (n) {
... return n * factorial(n - 1);
... });
>>> factorial(5);
120
The classic definition of map is easily expressed via pattern matching.
>>> var greedyMap = wu.match([ wu.___, [] ],
... [],
... [ Function, Array ],
... function (fn, arr) {
... return [fn(arr[0])].concat(map(fn, arr.slice(1)));
... }
... );
>>> greedyMap(function (n) { return n * n; }, [1,2,3,4])
[1, 4, 9, 16]
Lego functions
wu.autoCurry
wu.autoCurry(fn)
wu.autoCurry(fn, numArgsExpected)
Function decorator that automatically provides currying to the given
function. Optionally, pass a number as the second argument to signify
the maximum number of arguments to curry. If it is missing, we default
to fn.length.
>>> var add = wu.autoCurry(function (a, b, c) { return a + b + c; });
>>> add(1)(1)(1);
3
>>> add(1)()(1)()(1);
3
>>> add(1)(1, 1);
3
>>> add(1, 1, 1);
3
wu.bind
wu.bind(context, fn)
wu.bind(context, fn, arg1, arg2, ..., argN)
Return a new function which returns the value of fn when
called with context as this. Optionally,
curry arguments as well.
>>> var items = [1,2,3,4]; >>> var pop = items.pop; >>> pop(); undefined >>> items; [1, 2, 3, 4] >>> pop = wu.bind(items, items.pop); >>> pop(); 4 >>> items; [1, 2, 3]
wu.compose
wu.compose(fn1, fn2, ..., fnN)
Compose multiple functions to create a new function. For
example, wu.compose(f, g, h)(x) is equivalent
to f(g(h(x))).
>>> function square(x) { return x * x; }
>>> function add2(x) { return x + 2; }
>>> wu.compose(square, add2)(5);
49
>>> wu.compose(add2, square)(5);
27
wu.curry
wu.curry(fn, arg1, arg2, ..., argN)
Return a new function that calls fn with the
first n arguments frozen.
>>> function add(a, b) { return a + b; }
>>> var add3 = wu.curry(add, 3);
>>> add3(5);
8
wu.not
wu.not(fn)
Returns a function that, when called, returns the boolean negation of
applying its arguments to fn.
>>> wu([4, 5, 6]).all(wu.not(wu.match([ Number ], false, ... [ wu.___ ], true))); true
wu.partial
wu.partial(fn, arg1, arg2, ..., argN)
Similar to currying, but instead of freezing the first n
arguments, you can select which ones to freeze and which to leave free
with wu.___.
>>> function sub(a, b) { return a - b; }
>>> var sub3 = wu.partial(sub, wu.___, 3);
>>> sub3(5);
2
Utilities
wu.eq
wu.eq(a, b)
Returns true if the a and b are
equivalent. Performs an "as-deep-as-needed" comparison on complex
structures such as arrays and objects. With primitive objects, uses
the === operator.
>>> wu.eq({}, { foo: 1 });
false
>>> wu.eq({ foo: { bar: 1 } }, { foo: { bar: 1 } });
true
>>> wu.eq([1, [2], {}], [1, [3], {}]);
false
>>> wu.eq(null, false);
false
>>> wu.eq([1, 2, [4, 5], { foo: {} }], [1, 2, [4, 5], { foo: {} }]);
true
wu.memoize
wu.memoize(fn)
Returns a new function that caches the results of calling the
function fn. Arguments are keyed in the cache
using JSON.stringify().
Important: If the JSON object is not
globally available, wu.memoize will return the original
function and will not cache function calls. In older browsers that
don't support native JSON (notably IE7 and lower), this means you will
need to include
the json2.js script for
wu.memoize to work correctly.
>>> var factorial = function (n) {
... console.log("Calling factorial");
... return n === 0 ? 1 : n * factorial(n - 1);
... };
>>> factorial(3);
Calling factorial
Calling factorial
Calling factorial
Calling factorial
6
>>> var mfactorial = wu.memoize(factorial);
>>> mfactorial(3);
Calling factorial
Calling factorial
Calling factorial
Calling factorial
6
>>> mfactorial(3);
6
wu.toArray
wu.toArray(thing)
If thing is an iterator,
return thing.toArray(). Otherwise,
return Array.prototype.slice.call(thing). Useful for
converting a function's arguments object to a true array.
>>> (function () { return wu.toArray(arguments); }(1, 2, 3, 4))
[1, 2, 3, 4]
wu.toBool
wu.toBool(thing)
Forced coercion to boolean.
>>> wu.toBool(0) false >>> wu.toBool(1) true >>> wu.toBool(undefined) false >>> wu.toBool(Infinity) true
wu.noConflict
wu.noConflict()
If you ever run in to issues where the global name "wu"
is already taken and you still want wu.js to play nice,
there is a wu.noConflict function. It will make wu.js
remove itself from the global object, replace whatever used to live
at "wu", and return the library. You might use it like
this:
(function (wu) {
// In here, we can still access wu
}(wu.noConflict());
// Out here, only the old wu exists
Contributing
To get involved in the development of wu.js, just fork
the repository on Github. When you have something juicy for me, send
me a pull request.
Tests
See the results of the tests right in your browser. If any don't pass, please file a ticket in the issue tracker detailing which test failed and what browser you are running!