wu.js


A lazy, functional Javascript library that ain't nuthin' ta f*ck wit.


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:


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:

  1. Replace this iterator instance's .next() method with
    function () { throw new StopIteration(); }
    so that the remaining items in your iterator are no longer accessible.
  2. Throw a StopIteration error 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!