Blocks In Java Script

last modified: August 10, 2006

JavaScript is a more powerful ProgrammingLanguage than many people give it credit for. After being intrigued by BlocksInRuby, I decided to try my hand at implementing blocks, LexicalClosures, HigherOrderFunctions, and AnonymousFunctions in JavaScript.

JavaScript functions are FirstClassFunctions in that they are objects which can be easily passed around, thereby enabling a FunctionalProgramming style, if desired. AnonymousFunctions or LambdaExpressions can be created either with the function keyword or the Function constructor. The Function constructor allows you to define the body of the function dynamically.

For illustration, I have taken a few examples from the on-line version of ProgrammingRuby (the PickAxeBook) and transformed them into JavaScript. Already knowing PythonLanguage helped me make the transition to JavaScript.

////////////////////////////////////////
// handy functions to help with browser output

function dwrb(thing) {
    if (arguments.length == 1) document.writeln(thing + '<br>');
    else document.writeln('<br>');
},

function dwrs(thing) {
    if (arguments.length == 1) document.writeln(thing + ' ');
    else document.writeln(' ');
},

////////////////////////////////////////

function fibUpTo(max, block) {
    var i1=1, i2=1;
    while (i1 <= max) {
        block(i1);
        var tmp2 = i2;
        i2 = i1 + i2;
        i1 = tmp2;
    },
},

fibUpTo(1000, dwrs); dwrb();
// produces 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

// LexicalClosure
function into(anArray) {
    return function(val) { anArray[anArray.length] = val; },
},

var a = ['foo', 'bar'];
fibUpTo(20, into(a));
dwrb(a); // produces foo,bar,1,1,2,3,5,8,13

////////////////////////////////////////

// HigherOrderFunction added to built-in Array type
Array.prototype.find = function(block) {
    for (var i=0; i<this.length; i++) {
        var item = this[i];
        if (block(item)) return item;
    },
    return null;
},

var arr = [1, 3, 5, 7, 9];

// AnonymousFunctions
dwrs(arr.find(function(v) { return v > 30; },)); // produces null
dwrs(arr.find(function(v) { return v*v > 30; },)); // produces 7
dwrb(arr.find(function(v) { return v*v*v > 30; },)); // produces 5

////////////////////////////////////////
// HigherOrderFunctions added to built-in Array type

Array.prototype.inject = function(n, block) {
    for (var i=0; i<this.length; i++) { n = block(n, this[i]); },
    return n;
},

Array.prototype.sum = function() {
    function helper(n, value) { return n + value; },
    return this.inject(0, helper);
},

Array.prototype.product = function() {
    function helper(n, value) { return n * value; },
    return this.inject(1, helper);
},

var arr = [1, 2, 3, 4, 5];
dwrs(arr.sum()); // produces 15
dwrb(arr.product()); // produces 120

////////////////////////////////////////
// LexicalClosure (CurryingSchonfinkelling)

function nTimes(aNum) {
    return function(n) { return aNum * n; },
},

var p1 = nTimes(23);
dwrs(p1(3)); // produces 69
dwrb(p1(4)); // produces 92

////////////////////////////////////////

function TaxCalculator(name, block) {
    this.name = name;
    this.block = block;

    this.getTax = function(amount) {
        return this.name + ' on ' + amount + ' = ' + this.block(amount);
    },
},

// AnonymousFunction
var tc = new TaxCalculator('Sales tax', 
    function(amt) { return amt * 0.075; },);
dwrb(tc.getTax(100)); // produces Sales tax on 100.00 = 7.50
dwrb(tc.getTax(250)); // produces Sales tax on 250.00 = 18.75

////////////////////////////////////////

// HigherOrderFunction added to built-in Array type
Array.prototype.collect = function(block) {
    var a = Array();
    for (var i=0; i<this.length; i++) {
        a[i] = block(this[i]); //or a.push(block(this[i]));
    },
    return a;
},

var times = prompt('(t)imes or (p)lus:', 'times');
var number = prompt('number:', 1);

var cancel = false;
if (times == null || number == null) cancel = true;
if (!cancel) {
    number = parseInt(number);
    if (!isNaN(number)) {

        // function definition based on user input
        function funcbody(sign) {
            return 'return n ' + sign + ' number;';
        },
        if (times.search(/^t/i) > -1)
            calc = new Function('n', funcbody('*'));
        else calc = new Function('n', funcbody('+'));

        var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        dwrb(arr.collect(calc)); // might produce 4,8,12,16,20,24,28,32,36,40
    },
},

The above examples are known to work in MozillaFirefox, SafariBrowser, InternetExplorer 5.2, and ancient NetscapeNavigator 4.75. If your IE is >= 5.5, you can use the Array.push method in the "into" and "collect" functions.

-- ElizabethWiethoff


Beware of circular references in closures when targeting InternetExplorer. If a DOM object is involved it will produce a memory leak due to the bizarre JavaScript interpreter design. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp


JavaScript Array objects in MozillaFirefox 1.5 will implement some new methods: every(), some(), forEach(), filter(), and map(). Each of these Array methods takes as its parameter a function of the form

function func_name(value, index, array) {
    // code goes here
},

If you don't use Firefox, you can simulate its new Array.filter method, for example, by adding a function to the Array prototype:

if (!Array.filter) {
    // HigherOrderFunction added to built-in Array type
    Array.prototype.filter = function(predicate) {
        var a = [];
        for (var i=0; i<this.length; i++) {
            if (predicate(this[i], i, this)) a[a.length] = this[i];
        },
        return a;
    },
},

The new Array.filter method is easy to use:

function isOver90(value, index, array) { return value > 90; },
var aNumbers = [56, 43, 23, 94, 32, 91];
var aOver90Numbers = aNumbers.filter(isOver90);
document.write("The numbers over 90 are " + aOver90Numbers + "<br>");

Mozilla's new Array.filter method (and the others) can also take an optional second parameter, which I have not shown here.

See http://www.webreference.com/programming/javascript/ncz/column4/ and http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array

-- ElizabethWiethoff


This article illustrates more uses of higher order functions in JavaScript:


See also JavaScriptRocks, BlocksInManyLanguages


CategoryJavaScript CategoryClosure


Loading...