Game Development by Sean

JavaScript Delegates w/ Currying Placeholders

Table of Contents

A particularly powerful part of JavaScript is its vaguely functional aspects. However, they can at times be painful to use. The this name will rebind in surprising ways when passing around functions and there’s no native way to curry parameters, though you can of course form closures.

I’ve written a little library that gives something akin to std::bind from C++, but with the additional benefit of supporting the this context automatically.

The usage is pretty simple.

    delegate(obj, method)

    delegate(this, this.method)

    delegate(this, func);

Nice and simple and about as obvious and concise as you can get. One could support passing a string as the second parameter to avoid needing to specify the object twice in some cases, I suppose, but then you’d like code completion and refactoring support in the editors that have them.

Once you have a delegate, you can call it like any function and it Just Works(tm). Arguments are forwarded on as expected, the return value is returned as expected, and so on.

But wait, I’ve also added currying! With this feature, you can bind extra parameters to the delegate itself so they’re automatically passed on. You can even use placeholders so you can mix in parameters to the delegate, change their order, or so on.

    // bind a binary function delegate but swap its inputs
    delegate(func, bind(1), bind(0));

    // bind a trinary function as a binary function with a curried parameter
    delegate(func, bind(), "foo", bind());

    // bind a unary function into one that takes 6 parameters and ignore the first 5
    delegate(func, bind(5));

I’ve found these all to be incredibly useful for event callbacks and the like while writing NodeJs code. In many cases I need to bind a callback to simply invoke some method with a static argument. In a few cases I need to support a mixture of static arguments and ones from the event callback. This delegate module lets me do all that!

There’s really not much to it:

    (function(){
      function binder(n) { this.index = n; }
      function bind(n) { return new binder(n); }

      function delegate(obj, cb) {
        if (arguments.length > 2) {
          var curry = [].splice.call(arguments, 2);

          var bound = false;
          for (var i = 0; i < curry.length; ++i) {
            if (curry[i] instanceof binder) {
              bound = true;
              break;
            }
          }

          if (bound) {
            return function(){
              var params = [];
              var next = 0;
              for (var i = 0; i < curry.length; ++i) {
                if (curry[i] instanceof binder) {
                  var index = curry[i].index;
                  if (index === undefined) {
                    params.push(arguments[next++]);
                  } else {
                    params.push(arguments[index]);
                    next = index + 1;
                  }
                } else {
                  params.push(curry[i]);
                }
              }

              return cb.apply(obj, params);
            }
          } else {
            return function() { return cb.apply(obj, curry); }
          }
        } else {
          return function(){ return cb.apply(obj, arguments); }
        }
      }

      module.exports = delegate;
      module.exports.delegate = delegate;
      module.exports.bind = bind;
    })();

And here’s a little silly bit of test code to show how it works.

    var delegate = require('delegate')
      , bind = delegate.bind;

    var obj = {
      a:1,
      b: 'str',
      m: function() { return this.b; },
      d: function(v,u) { return this.a + v + u; }
    };
    function f(v,u) { return v + u; }

    function call(cb) {
      var params = [].splice.call(arguments, 1);
      console.log(cb.apply(this, params));
    }

    call(delegate(obj, obj.m)); // prints str
    call(delegate(obj, obj.d, 1, 2)); // prints 4
    call(delegate(obj, obj.d, bind(), 4), 7); // prints 12
    call(delegate(obj, obj.d, 4, bind(5)), 0, 1, 2, 3, 4, 5); // prints 10

    var d = delegate(obj, obj.d, bind(), bind(3));

    call(d, 7, 4, 5, 6); // prints 14

    d = delegate(this, f, bind(1), bind(0));

    call(f, "a", "n"); // prints ab
    call(d, "a", "b"); // prints ba

I note that the code could be made a slight bit prettier and more compact with the use of a helper library that extends Array with some useful methods, but I like the way the code right now has no extra module dependencies.

GitHub link: https://github.com/seanmiddleditch/js-delegate.git NPM link: https://npmjs.org/package/js-delegate</div>