This article is part of the article series "Node.JS Modules You Should Know About."
<- previous article next article ->
node logo

Hey everyone! This is the third post in my new node.js modules you should know about article series.

The first post was about dnode - the freestyle rpc library for node, the second was about optimist - the lightweight options parser for node.

This time I'll introduce you to one of my own modules called node-lazy - lazy lists module for node.

Basically you create a new lazy object, and pump data into it via data events (it's an event emitter). Then you can manipulate this data via chaining by using various functional programming methods.

Here is a quick example. Here we create a new lazy object and define a filter that returns only even integers, then we take just 5 elements, then we apply map on them, and finally we join (think of threads) the result in a list:

var Lazy = require('lazy');

var lazy = new Lazy;
lazy
  .filter(function (item) {
    return item % 2 == 0
  })
  .take(5)
  .map(function (item) {
    return item*2;
  })
  .join(function (xs) {
    console.log(xs);
  });

You can return this object from your function and then later when someone pumps data into it via data events, it will do the computation.

For example, if you do this:

[0,1,2,3,4,5,6,7,8,9,10].forEach(function (x) {
  lazy.emit('data', x);
});
setTimeout(function () { lazy.emit('end') }, 100);

Then the output will be produced by the console.log once 5 elements have reached the bottom of the chain.

The output is: [0, 4, 8, 12, 16].

Here is a real world example from node-iptables (another of my modules):

var Lazy = require('lazy');
var spawn = require('child_process').spawn;
var iptables = spawn('iptables', ['-L', '-n', '-v']);

Lazy(iptables.stdout)
    .lines
    .map(String)
    .skip(2) // skips the two lines that are iptables header
    .map(function (line) {
        // packets, bytes, target, pro, opt, in, out, src, dst, opts
        var fields = line.trim().split(/\s+/, 9);
        return {
            parsed : {
                packets : fields[0],
                bytes : fields[1],
                target : fields[2],
                protocol : fields[3],
                opt : fields[4],
                in : fields[5],
                out : fields[6],
                src : fields[7],
                dst : fields[8]
            },
            raw : line.trim()
        };
    });

This code fragment takes output from iptables -L -n -v and converts it into a data structure for later use.

Here a new Lazy object is created from an existing stream - the iptables.stdout stream. Next, the special lines getter is called that splits the stream on \n char and converts it into a line-stream. Then this stream is mapped onto String constructor to convert it to a string. Next, first two lines are skipped via skip(2) and then all the other lines are converted into a data structure via map.

You can also create all kinds of ranges with node-lazy, including infinite ranges. Check this out, ranges.js:

var Lazy = require('lazy');

Lazy.range('1..20').join(function (xs) {
    console.log(xs);
});

Lazy.range('444..').take(10).join(function (xs) {
    console.log(xs);
});

Lazy.range('2,4..20').take(10).join(function (xs) {
    console.log(xs);
});

When you run it:

$ node ranges.js 
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]
[ 2, 4, 6, 8, 10, 12, 14, 16, 18 ]
[ 444, 445, 446, 447, 448, 449, 450, 451, 452, 453 ]

Here are all the possible ranges that node-lazy supports:

Lazy.range('10..')       - infinite range starting from 10
Lazy.range('(10..')      - infinite range starting from 11
Lazy.range(10)           - range from 0 to 9
Lazy.range(-10, 10)      - range from -10 to 9 (-10, -9, ... 0, 1, ... 9)
Lazy.range(-10, 10, 2)   - range from -10 to 8, skipping every 2nd element (-10, -8, ... 0, 2, 4, 6, 8)
Lazy.range(10, 0, 2)     - reverse range from 10 to 1, skipping every 2nd element (10, 8, 6, 4, 2)
Lazy.range(10, 0)        - reverse range from 10 to 1
Lazy.range('5..50')      - range from 5 to 49
Lazy.range('50..44')     - range from 50 to 45
Lazy.range('1,1.1..4')   - range from 1 to 4 with increment of 0.1 (1, 1.1, 1.2, ... 3.9)
Lazy.range('4,3.9..1')   - reverse range from 4 to 1 with decerement of 0.1
Lazy.range('[1..10]')    - range from 1 to 10 (all inclusive)
Lazy.range('[10..1]')    - range from 10 to 1 (all inclusive)
Lazy.range('[1..10)')    - range from 1 to 9
Lazy.range('[10..1)')    - range from 10 to 2
Lazy.range('(1..10]')    - range from 2 to 10
Lazy.range('(10..1]')    - range from 9 to 1
Lazy.range('(1..10)')    - range from 2 to 9
Lazy.range('[5,10..50]') - range from 5 to 50 with a step of 5 (all inclusive)

Install it via npm, as always:

npm install lazy

Awesome sauce!

If you love these articles, subscribe to my blog for more, follow me on Twitter to find about my adventures, and watch me produce code on GitHub!

Sponsor this blog series!

Doing a node.js company and want your ad to appear in the series? The ad will go out to 14,000 rss subscribers, 7,000 email subscribers, and it will get viewed by thousands of my blog visitors! Email me and we'll set it up!

This article is part of the article series "Node.JS Modules You Should Know About."
<- previous article next article ->

Comments

Brennan Falkner Permalink
December 05, 2011, 17:08

I wrote something like this in Ruby once...
Usage Implementation

Definitely useful.

December 26, 2011, 09:21

Hi Peteris!
Great post!
There is a typo:

 // ...
Lazy.range('[1..10)')    - range <b>g</b>rom 1 to 9
// ...
December 26, 2011, 16:17

Thanks for reporting the type. Fixed now!

January 18, 2012, 00:05

There's an awesome stream module called streamer that takes a functional approach to providing nearly the same functionality.

Any thoughts on the benefits of streamer vs lazy?

Great Info. Thanks for the your effort. Appreciate it.

Leave a new comment

(why do I need your e-mail?)

(Your twitter name, if you have one. (I'm @pkrumins, btw.))

Type the word "quake3_284": (just to make sure you're a human)

Please preview the comment before submitting to make sure it's OK.

Advertisements