JavaScript Partial Application with bind()

Sometimes the subtle ways other people use JavaScript puts a smile on my face. I'm getting started with the Mongoose driver for MongoDB; while I was reading the quick start guide I came across the following line.

db.on('error', console.error.bind(console, 'connection error:'));  

I'd seen the bind() method before but I hadn't ever used it for anything so it's use here confused me a little. It is a prototype method defined in the ECMASCRIPT 5 specification. The method returns a new function bound to a new execution context. The first argument passed to bind() is the object to bind this to.

var myObj = {  
    someValue: 'Hello World!',
    myFunc: function () {
        return this.someValue;
    }
};
myObj.myFunc(); // returns "Hello World!"  
var newObj = {  
    someValue: 'A new value!'
};
var newFunc = myObj.myFunc.bind(newObj);  
newFunc(); // returns "A new value!"  

That's not all bind() does however, it also does partial application for you. Any arguments passed to bind() after the first argument are bound to the arguments of the original function. This sounds complicated but it's not. It's just hard to describe so I'll show you instead.

var operations = {  
    sum: function (a, b) {
        return a + b;
    }
};
operations.sum(9000, 1); // returns 9001  

The above function simply adds two numbers and returns the sum, but watch what happens if we create a new function using bind().

var add9000 = operations.sum.bind(operations, 9000);  
add9000(5); // returns 9005  

As I said earlier, the first argument is the object bound to the execution context. In this example we don't really want to change the execution context object so we pass in operations which is the object the method was already bound to anyway. The next argument is where it gets interesting. The value of 9000 is bound to the first argument of operations.sum. This is called partial application or currying. The new function returned from bind() is assigned to the variable add9000. When we call add9000 and pass in arguments the function will essentially do the work of the original function except the first argument is already bound to a value and the rest of the arguments are passed in as usual. This is why we can just pass in 5 and get back 9005.

Now that we understand bind() well enough we can go back to the original line of code that was confusing me. The first thing it's doing is really obvious; we are creating a handler for an error event. The error event passes an error object to the handler function we pass in. A really easy way to handle this with very little code would be to just pass in the console.error function and let it use that as the handler.

db.on('error', console.error);  

Now whenever the event is triggered it will pass the error object into the console.error function and it will be displayed in the JavaScript console. But in the case of the original line of code they wanted to prefix these errors with a message "connection error:". My immediate instinct would be to write something like the following.

db.on('error', function (err) {  
    console.error('connection error:', err);
});

There is functionally no difference and the end result would be the same, but the way they did it was a clever way of saving a little typing.

db.on('error', console.error.bind(console, 'connection error:'));  

Hopefully you can see now just how simple that line of code really is. I very much enjoy encountering little things like this that cause me to learn something new. Even though I knew of bind() already, I had never encountered a practical usage of it. Reading programming text books is great and I learn a lot, but sometimes it takes seeing some things out in the wild before it really sticks.

Chev

Read more posts by this author.

comments powered by Disqus