Tuesday, January 29, 2008

Currying in JavaScript: Fun for the Whole Family!

Files referenced: curry.js 2kb

As I've said before, I'm quite partial to JavaScript. Lately, I've been reading about currying functions, so let's see if currying can be done in JavaScript.

The begs the question, What is currying? Currying is a technique to transform a function that takes some number of arguments to a function that takes fewer. The process was named after the logician Haskell Curry. The goal is to get something that works like this:

function add(a, b)
{
return a + b;
}

alert(add(3, 4)); // 7

var add_3 = add(3);

alert(typeof add_3); // function
alert(add_3(4)); // 7

When we pass fewer than the required number of arguments, it returns a function that takes the remaining arguments. Once we pass the correct number of parameters, it evaluates the function.

Before possibly reinventing the wheel, I looked online to see if anyone had done this. While there are plenty of JavaScript functions that claim to curry, they all get it wrong. They do something like this (using the same add from above):

var add_3 = curry(add, 3);

alert(add_3(4)); // 7
alert(typeof add(3)); // number

They do get the add_3 function in the end, but all they do is create a new function with fewer arguments. Any calls to add will still behave like any other JavaScript function with too few arguments; the remaining arguments will be undefined. The add_3 function (both times) is called a partial function.

So how do we do proper currying in JavaScript? Here's what I did. It takes three steps.

First, we need a way to change a function's arity. Arity is the number of parameters a function accepts. This may not seem directly relevant, but trust me:

Function.prototype.toArity = function (n)
{
var func = this;
var parmString = '';
var funcString = '';
var i;

if (n == func.length)
{
return func;
}

if (n == 0)
{
return function ()
{
return func.apply(this, arguments);
};
}

for (i = 0; i < n; ++i)
{
parmString += 'a' + i;

if (i < n - 1)
{
parmString += ',';
}
}

funcString = '(function (' + parmString + ') { return func.apply(this, arguments); })';
return eval(funcString);
};

This function looks complicated, but it's not really. The basic idea is to wrap the function call in another function of the desired arity. They only way to do this in JavaScript is to create the function at runtime with eval. I know most people (myself included) advocate against eval, but in this case it is truly the only way. So this function will convert something like this:

function iTake3Args(a, b, c)
{
return a + b; // Ignore c
}

via this call:

iTake3Args.toArity(2);

to something like this:

function anonymous(a0, a1)
{
return func.apply(this, arguments);
}

It may look ugly, but it works. And no-one should be printing the internals of the function anyway (they'll be calling it).

So now that we can control the arity of functions, we can generate partial functions (what all the libraries do) correctly (with the property arity). The function goes like this:

Function.prototype.partial = function ()
{
var partialArgs = Array.prototype.slice.call(arguments, 0);
var func = this;

var retFunc = function ()
{
var args = Array.prototype.slice.call(arguments, 0);
return func.apply(this, partialArgs.concat(args));
};

return retFunc.toArity(Math.max(0, func.length - partialArgs.length));
};

There's quite a bit going on here. Why is the arguments object being passed to slice? The arguments object is just that, an object. We need to convert it to a proper array so we can concatenate the two argument arrays in the partial function. Then the return function (with arity 0) is converted to the proper arity. The function works similarly to the other curry functions:

var add_3 = add.partial(3);

alert(add_3(4)); // 7

Now we can finally get to currying properly. Here's the function:

Function.prototype.toCurriable = function ()
{
var func = this;

var retFunc = function ()
{
if (arguments.length < func.length)
{
return func.partial.apply(func, arguments).toCurriable();
}

return func.apply(this, arguments);
};

return retFunc.toArity(func.length);
};

If you've understood everything so far, this shouldn't be too out there. If there aren't enough arguments, a partial function will be returned. Otherwise, the function is evaluated. The return function (with arity 0) is converted to the proper arity. So finally, we can recreate our initial example (with a couple changes):

var add = function (a, b)
{
return a + b;
}.toCurriable();

alert(add(3, 4)); // 7

var add_3 = add(3);

alert(typeof add_3); // function
alert(add_3(4)); // 7

Note the change to the definition of add. It has to be a function expression rather than a function definition because we want the result of toCurriable, not the original function.

This is by no means a perfect solution. While it preserves arity, it does not preserve scope. Things like:

someObj.func(3)(4);

won't work as expected, because the second function call is evaluated in global scope rather than on someObj. I don't see any way to fix this with my current functions.

Also, this solution relies on closures and eval, which means it will be memory intensive and will be slow in some browsers. I have not tested this in any browsers, only in Mozilla's Rhino, a command-line JavaScript environment. I imagine it will work in any Mozilla product. I make no claims about Internet Explorer.

I do, however, feel that this is an elegant and unobtrusive solution that has not been done before. This was a purely theoretical endeavor. True currying is definitely possible in JavaScript.

I hope someone found this useful. You can download all of the above functions in one JavaScript file at the top of this entry.

Labels: ,

Monday, April 23, 2007

JavaScript is powerful, people. Believe me.

There are many people who still believe that JavaScript is somehow an inferior programming language, and that Java or C++ is somehow better. Today I shall attempt to put some of these beliefs to rest.

First a little history. JavaScript started in the Netscape browser as a new feature to combat the competing Internet Explorer (we can see how well that worked out). It was originally called Mocha, but Netscape decided to change the name to JavaScript after a deal with Sun to include Java technology in the browser. Of course, Microsoft had to have their own JavaScript, and called their JScript to avoid potential trademark concerns. As of this blog, the latest JavaScript is 1.7.

JavaScript and Java are, for the most part, only related by name. Their similar syntax is because both are derived from the C programming language (about which I have previously commented). While Java is a statically typed language (each variable has a specific type), JavaScript is dynamically typed (variables' types are determined by context). It is this superficial similarity that leads to our first misconception.

1. Doesn't JavaScript lack support for Object Orientation?


Many programmers who are not practiced in JavaScript will say that JavaScript does not support object oriented programming because it does not support classes. This belief is, of course, wrong. It is true that JavaScript does not use the traditional Class-based paradigm used by C++ and Java, in which objects are instances of classes, and new classes can be built by inheriting from other classes. Instead, it follows the Prototype-based paradigm, in which new objects can be built by cloning an existing object or ex nihilo (“from scratch”).

In JavaScript, a new object can be created in multiple ways. The first is through an object literal, such as:

var myObj = { property: "someValue" };

The second is through a constructor function, like the following:

function MyType(a)
{
    this.property = a;
}

var myObj = new MyType("someValue");


Both create a new object with one property, whose value is the string "someValue". The second is more like the C++ and Java style of object creation and simulate a class.

JavaScript (and all Prototype-based language) also provide a mechanism for inheritance. This is where the prototype comes in to play. Just what is the prototype? It is an object upon which other objects are built. For example:

function BaseType()
{
    this.funcA = function()
    {
        alert("Base::funcA");
    };

    this.funcB = function()
    {
        alert("Base::funcB");
    };
}

function DerivedType()
{
    this.funcB = function()
    {
        alert("Derived::funcB");
    };
}

DerivedType.prototype = new BaseType();

var myDeriv = new DerivedType();
myDeriv.funcA();
myDeriv.funcB();


The above will display the following:

Base::funcA
Derived::funcB


Every DerivedType starts out with all the functions of a BaseType, but the constructor then overrides the property funcB with its own version. This is inheritance.

2. Isn't JavaScript only used in web pages?


This one is pretty easy to refute. JavaScript is used in the core of the Mozilla Firefox browser, and when writing extensions one uses JavaScript. There is even a command line version of the JavaScript engine, so that JavaScript shell scripts can be made.

3. Doesn't JavaScript not always work? Isn't it unstandardized?


The official standard for JavaScript is called ECMAScript. If there is a standard, why does JavaScript work in some browsers and not in others? The question answers itself: not all browsers implement the same JavaScript. They all implement ECMAScript, but this is the barest form of JavaScript. The parts that usually vary are the DOM and the event model.

The DOM (Document Object Model) is a way of expressing XML documents (usually the HTML page) as a collection of objects in parent-child-sibling relationships. The W3C has recommended a standard DOM, defining objects and their functions. Most browsers follow this recommendation in their implementation of ECMAScript, but some (read: MS Internet Explorer) do not. This makes writing cross-browser scripts that use the DOM difficult, and has led many to think that JavaScript is unreliable.

var xyz = document.getElementByID('xyz');

The same code in JScript:

var xyz = document.all['xyz'];

Another issue is the event model. Again, there is a recommended standard from the W3C for how events should work. And again, some browsers (guess who!) have decided not to use the standard.

JavaScript:
function listener(e)
{
    alert(e.srcElement);
}
xyz.addEventListener('onclick', listener, false);


The above in JScript:

function listener()
{
    alert(window.event.srcElement);
}
xyz.attachEvent('onclick', listener);


There are a few significant differences. First, notice that in the W3C standard, the listener function takes the event object as a parameter, but the JScript version has a global event object. Secondly, notice that the implementations use different functions with different numbers of parameters to add an event to an object.

Act III: Epilogue


I hope we've learned something from this rant. JavaScript is not to be taken lightly; it can be used to do anything another programming language can do, because it is what is a Turing-complete language. JavaScript is sometimes difficult to use in web pages because different browsers use different implementations of the ECMAScript standard, but most browsers use compatible language features, but Internet Explorer does not.

Labels: , ,