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