catDir
I'll give you a simple problem. Find the cats in a
directory! Write a function that recursively walks a directory
tree and prints the contents of every file, except for those starting with
a dot. In NodeJS, using the asynchronous API.
In other words, we're looking for (almost) the equivalent of:
find . -type f -exec cat {} \;
with the difference that it should not search directories that start with
a dot, nor include files that start with a dot. I will start optimistic:
it's easier to write that in Node than to figure out how to do it with the
classic find
utility.
In NodeJS
The program isn't very hard to write in plain Node if you have some experience, but it looks ugly:
"use strict";
var fs = require("fs");
var path = require("path");
var sys = require("util");
function catDir(dirname, callback) {
fs.readdir(dirname, function(err, files){
if (err) throw new Error(err);
(function loop(i){
if (i < files.length) {
var f = files[i];
if (f.charAt(0) != ".") {
var fullname = path.join(dirname, f);
fs.stat(fullname, function(err, stat){
if (err) throw new Error(err);
if (stat.isDirectory()) {
catDir(fullname, function(){
loop(i + 1);
});
} else if (stat.isFile()) {
fs.readFile(fullname, "utf8", function(err, data){
if (err) throw new Error(err);
sys.puts(data);
loop(i + 1);
});
}
});
} else {
loop(i + 1);
}
} else {
callback();
}
})(0);
});
}
console.time("catDir");
catDir("/home/mishoo/work/my/uglifyjs2/", function(){
// rest of the program goes here.
console.timeEnd("catDir");
});
In λanguage
Check it out, simplicity and elegance. It doesn't appear to, but it's
still using the asynchronous API — in other words,
catDir
won't block the process. But there's no callback
hell.
catDir = λ(dirname) {
forEach(readDir(dirname), λ(f){
if !stringStartsWith(f, ".") {
let (fullname = pathJoin(dirname, f),
stat = fstat(fullname)) {
if isDirectory(stat) {
catDir(fullname);
} else if isFile(stat) {
print(readFile(fullname));
}
}
}
});
};
catDir("/home/mishoo/work/my/uglifyjs2/");
Notes:
The program requires a primitive library which I've put on a separate page.
The λanguage is pretty limited at this point and we can't just do
f.charAt(0) != "."
, so I wrotestringStartsWith
as a primitive function, and use the newly added negation operator (which again, is syntactic sugar).The
readDir
andreadFile
functions are also primitives which call the respective functions from NodeJS's “fs” module. Again, they are using the asynchronous versions. Same goes forfstat
.You might object that I cheated a bit, using
forEach
, while in JavaScript we have an uglyloop
function, but here's the thing: in λanguage we can use something that looks like a synchronous loop; in JavaScript we can't (even with helpers like async.eachSeries, you still need to nest the rest of the code in a continuation that you pass to the looping abstraction).The speed difference vs. plain NodeJS is negligible. Funny, even if I disable the optimizer, the performance does not degrade much. That's why 10x slower than JavaScript for a hot loop should not scare you — in real apps, the bottleneck is usually not in those loops; and when it is, you can just write primitives.