Thoughts from the office by Ed Ball
Tuesday, April 05, 2005

My last post described the BindArguments function – it returns an anonymous function that, when called, calls another function with the arguments originally specified in the call to BindArguments. Put another way, BindArguments converts a function that takes n arguments into a function that takes 0 arguments. But what if we need a function that takes 1 argument?

Imagine we have a Transform function that transforms an array of numbers in place:

function Transform(array, fn)
{
  ForEach(array, function (x, n) { array[n] = fn(x); });
}

We want to use this Divide function to do the transformation.

function Divide(n1, n2)
{
  return n1 / n2;
}

If we want to invert the numbers in the array:

Transform(array, function (x) { return Divide(1, x); });

Can we write a binding function like BindArguments that allows us to avoid the explicit anonymous function? BindArguments won’t work, because it returns a function that takes 0 arguments. If the returned function is called with any arguments, those arguments are ignored. We need the returned function to accept an argument and call Divide with 1 and that argument. We’ll call it BindFirst, because it accepts a function that takes two arguments and binds the first one.

Transform(array, BindFirst(Divide, 1));

In other words,

BindFirst(fn, x)(y) === fn(x, y)

Can we write such a function? Of course!

function BindFirst(fn, arg)
{
  return function (x) { return fn.call(this, arg, x); };
}

We can do better and create a function that will bind any number of arguments before any number of additional arguments.

// BindFirst(fn, x1, x2, …, xn)(y1, y2, …, yn) === fn(x1, x2, …, xn, y1, y2, …, yn)
function BindFirst(fn)
{
  var args = [];
  for (var n = 1; n < arguments.length; n++)
    args.push(arguments[n]);
  return function ()
    {
      var myargs = [];
      for (var m = 0; m < arguments.length; m++)
        myargs.push(arguments[m]);
      return fn.apply(this, args.concat(myargs));
    };
}

Outstanding. But wait; what if we want to divide the numbers in our array by 2?

Transform(array, function (x) { return Divide(x, 2); });

What we need here is a BindSecond function.

// BindSecond(fn, x)(y) === fn(y, x)
function BindSecond(fn, arg)
{
  return function (x) { return fn.call(this, x, arg); };
}
Transform(array, BindSecond(Divide, 2));

Better yet, BindLast, whose implementation I leave as an exercise for the reader. (It is very similar to that of BindFirst.)

// BindLast(fn, x1, x2, …, xn)(y1, y2, …, yn) === fn(y1, y2, …, yn, x1, x2, …, xn)

Is it possible to write a truly generic Bind function that can be used in place of both BindFirst and BindLast and support even more complex binding scenarios? It is, and I hope to show it to you… in a future post.

4/5/2005 1:31:44 PM (Pacific Daylight Time, UTC-07:00) | Comments [1] | Code | JavaScript#
4/6/2005 8:22:06 AM (Pacific Daylight Time, UTC-07:00)
This is great stuff!

"Transform(array, BindFirst(Divide, 1));" looks very much like functional programming -- hauntingly like what Python's built-in functions (http://www.python.org/doc/2.4/lib/built-in-funcs.html) and Google's GooPy (http://goog-goopy.sourceforge.net/goopy.functional.html) were made for.
Peter Rust
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Search
Archive
Links
Categories
Administration
Blogroll