Skipping Computation altogether using Lazy.js  

If you’re a javascript developer, you’ve probably used underscore (or lo-dash). Underscore is an amazing library that really helps keep your code functional, and provides a whole bunch of simple methods to keep your code ‘for-loop-free’.

Underscore can really help keep your code terse, and minimal. However, sometimes, it can be wasteful in terms of CPU cycles. In other words, it could be faster.

There are a few ways that code can be made faster while solving a problem:

  1. Using a faster algorithm.
  2. Improving the code that implements your algorithm.

But there is another way to make your code faster that many people seem to overlook. (usually rightly so):

That, in a few short words, is what I think is the philosophy of Lazy.js. ‘Be lazy, and do as little work as possible.

Take this for an example. Let’s say you want to pass an array and map it to a new array by using multiple methods.

var newArr = _(array).chain()
                     .map(func1)
                     .map(func2)
                     .map(func3)
                     .pluck(5)
                     .valueOf();

This, is probably a relatively common way of using underscore. Chaining methods is awesome, and the code is amazingly terse. The problem is, that this code is extremely wasteful. This is what happens behind the scenes:

Imagine, if 'array’ had a million values in it. You just did computations on 999,995 values that you didn’t even need. Further, while you could have traversed the array just once, you actually did that three times. Once for each map function. If you were implementing the same logic without underscore, you probably would’ve done something like this:

var result = [];
for(var i = 0; i < 5; i++){
    result.push(func3(func2(func1(array[i]))));
}
return result;

Now, that code is WAY more efficient but, it’s not exactly readable. Also, it’s definitely not functional. Wouldn’t you want underscore to just do something like this behind the scenes for you?

Well, Lazy.js does exactly that. What’s even better, it works just like underscore. This is how you would accomplish the same thing with Lazy.js

var newArr = _(array).map(func1)
                     .map(func2)
                     .map(func3)
                     .pluck(5)
                     .valueOf();

You don’t need the chain() method, other than that, it is basically identical. As a downside, you need to call ValueOf even when you’re not chaining methods:

var newArr = Lazy(array).map(func).valueOf();

I think that’s a good compromise in the massive performance benefit.

If you’re convinced about the value of Lazy.js, you can probably choose to not read anymore. I was however, curious about how Lazy did it’s magic and am trying to implement something similar myself. I had reimplemented Underscore.js and I know what great learning experience it was. I hope, re-implementing a subset of Lazy.js will probably be super-useful too. I’m only implementing the map and pluck functionality, though, as this is little more than a learning exercise.

var Lazy = function(collection) {
    if (!(this instanceof Lazy)) {
        return new Lazy(collection);
    }

    this.collection = collection;
    this.mapFunctions = [];
    this.length = collection.length; 
    //I'm only accounting for arrays
};

Lazy.prototype.map = function(func) {
    this.mapFunctions.push(func);
    return this;
};

Lazy.prototype.pluck = function(num) {
    this.length = Math.min(num, this.length);
    return this;
};

Lazy.prototype.value = function() {
    var result = [];
    for (var i = 0; i < this.length; i++) {
        result.push(this.collection[i]);
        if (this.mapFunctions.length > 0) {
            for (var j = 0; j < this.mapFunctions.length; j++) {
                var thisFunc = this.mapFunctions[j];
                result[i] = thisFunc(result[i]);
            }
        }
    }
    return result;
};

And that’s it. The idea is to remember what to do, but not actually do it. Here I’m adding all the map functions to an array, and using all of them in a single iteration when value() is called. Here is how you would actually use my rudimentary implementation of a subset of Lazy.js:

var ans = Lazy([1, 2, 3, 4])
    .map(function(num) {
        return num * num;
    })
    .map(function(num) {
        return num + 1;
    })
    .map(function(num) {
        return num * 2;
    })
    .pluck(2)
    .value();

console.log(ans);

This should log [ 4, 10 ].

Please Note: This code is far from complete. For simplicity, calling map on a Lazy object, modifies the object itself. You may or may not want that behaviour.

 
34
Kudos
 
34
Kudos

Now read this

Dealing With Callback Hell

fs.readFile("myFile.txt", function(err, fileContents){ if(!!err) { throw Error(err); return; } myConvertFile(fileContents, function(err, newContents){ if(!!err){ throw Error(err); return; } fs.writeFile('myNewFile.txt', newFileContents,... Continue →