Functional Javascript

Lisp? scheme? erlang, haskell? forget about them! the most widely deployed functional programming language is javascript. Borrowing a trick or two from the transformers, javascript masquerades as a procedural language until you're ready to take it to the next level.

Procedural FunctionsFunctional Javascript

In the beginning there was the function…

function hello(who) {
   alert
('Hello '+who);   // outputs "Hello world"
}

hello
('world');

Nearly every programming language has a construct similar to this. Every programmer in the world is comfortable with this syntax. hello is the name of our function, but what happens if we make hello the same as a variable name?

Functions As Variables

It may be a little unfamiliar in syntax but most programmers should be comfortable with a function that's defined like a variable.

var hello = function (who) {
   alert
('Hello '+who);      // outputs "Hello world"
}

hello
('world');

There's no big difference here, just add a var tag, an equal sign and shift where hello appears, everything else is the same. We call it the same way, it does the same stuff.

Take a look at that definition again. This is more than just visual sugar -- a pretty way to define a function. This construct actually means something. It means that our function is going to follow the same rules and abilities as any other variable in javascript.

It will have the same scope and it can be passed to other functions just like any other variable.

Passing Functions To Functions

If we declare hello like a variable, it kind of makes sense that we can pass it like one as well.

var say = function(what) {
   what
('say function');
}

var hello = function (who) {
   alert
('Hello '+who);      // outputs "Hello say function"
}

say
(hello);

So in Javascript a function can be passed just like an array or an object or any other variable. Or put another way, anything that can hold a value in javascript can hold a function.

There are two ways to use a function as an argument. You can pass a pointer to the function itself, or you can execute the function in the call and pass the return value of the function. The example above passed a pointer of the hello function to the variable what in the say function. what then became a pointer to hello.

If you've ever made an event in Javascript you've probably already used the pass as pointer concept because all the event handlers want a pointer to a function to call when they're tripped. For instance…

function doSomething() {
   alert
('you pushed my button!');
}

document
.onclick=doSomething;

This example gives the function doSomething to the onclick event. Because we only used the function name and not doSomething(), onclick now has a pointer to doSomething. On the other-hand, if we were to use doSomething() things will still work out ok as long as doSomething() returns a pointer to a function…

function pushedMyButton() {
   alert
('you pushed my button!');
}

function doSomething() {
   
return pushedMyButton; // return a pointer to the pushedMyButton function
}

document
.onclick=doSomething();

Now when the onclick event is set up, doSomething is executed because we appended parenthesis at the end. The parenthesis are an empty set -- we're not passing anything -- but that's ok, they tell javascript that we want to execute the function not pass a pointer to it. If doSomething() passes back a pointer to another function then the event will actually still work. In this example, doSomething returns a pointer to a function, which is the only thing onclick wants. onclick doesn't care how it gets the pointer, it can take it either directly ( onclick=doSomething; ) , or as the result of a function returning one to it ( onclick=doSomething() ).

Just to recap; if you don't follow a function name with argument parenthesis then you're passing the function as a pointer. If you follow up a function name with argument parenthesis, even if they're just an empty set -- (), Javascript will execute the function and pass along the return value of the function. Keep that difference in mind, we'll be coming back to it a little later.

var test = function () {
   
return "This is a String";
}

var testCopy = test;      // testCopy is a pointer to the test function()
var testStr = test();     // testStr contians "This is a string"
var testing = testCopy(); // testing contains "This is a string"

Lambda, Lambda, Lambda

If you've mastered the concept of passing functions as arguments to other functions then congratulations! You now understand lambda which basically just means using a function as an argument in a call to another function. Lambda is a pretty big buzzword in functional programming and it gets thrown around a lot. Worse, it gets thrown around like everyone already knows that lambda is using a function in an argument in a call to another function. So if you see something like "Here we'll use a lambda expression" it just means they're passing a function as an argument.

Visually, in psuedo-code, Lambda is represented by the λ character. It is the 11th letter of the Greek alphabet. You'll also see argument variable names named lambda used to indicate that variable will be receiving a function.

As an aside, it's probably pure coincidence that Lambda Lambda Lambda was the fraternity in Revenge of the Nerds.

Anonymous Functions

Chances are, if you've ever written an Ajax routine or an Event handler in Javascript you are familiar with an anonymous function. For instance…

document.onmousedown = function() { alert("You pressed the mouse button"); }

This sets up an event handler whenever the mouse is pressed. When the mouse button is pressed, the handler calls the anonymous function which simply throws up an alert box. This function is anonymous because we didn't declare a name for the function, we can't call it later unless we somehow go through onmousedown.

Which leads us to another concept of functional programming. If a function doesn't have a name, and it's just another value in the machine, then functions in Javascript are much closer to data than what we would consider traditional functions and objects. It's data that does stuff to be sure, but in every place that matters, learning to look at the function as a piece of active data will really help you out when it comes to figuring out just how to apply this technology.

Self-Invoking Functions

Consider for a moment the following code…

var x = (function () {return(5+6)})();
document
.writeln(typeof(x)+'<BR>'); // Outputs: Number
document
.writeln(x);                // Outputs: 11;

This is an anonymous, self-invoking function and it's really quite powerful, although it's hard to see that power in the little example. What we did here was wrap a function in parenthesis and then add in the argument parenthesis. When javascript comes across this line it will execute the function and put the return value in x. That's it. The function existed only long enough to generate 11 and give it to X then it's gone. x isn't a function because we invoked the anonymous function inside the parenthesis with a set of argument parenthesis. When we ask for the typeof of x we get "Number" as the result -- not a function. And when we ask for the value of X we get 11 instead of the function.

A more common real-world example of this is the following bookmarklette code which attaches an external Javascript to the current page.

( function () { 
   
var s=document.createElement("script");
        s
.src="http://mydomain.com/script.js";
    document
.body.appendChild(s);
 
}
) ();

This code, when executed, creates a new script tag, sets its external source, then appends it to the document.body which will automatically load the external javascript file. Because it's a self-invoking function it works great in a link which can be dragged into the bookmark or link bars to make a bookmarklette…

<A HREF='javascript:(function(){var s=document.createElement("script"); s.src="http://mydomain.com/script.js"; document.body.appendChild(s);})();'>My First Bookmarklete App</A>

You can learn more about bookmarklettes in the article: Bookmarklets -- The Evil Lurking In Your Browser.

You can also use self-invoking functions lambda (buzzword!) as a sort of shortcut when you need to do some light processing of data before you pass it on to another function. Observe…

function doAlert(msg) {
   alert
(msg);
}

doAlert
( (function(animal1,animal2) { return animal1 + ' loves ' + animal2; } ) ('cat', 'dog') );

In this example we have a traditional doAlert(msg) function, but it's invoked with an anonymous, self-invoking function as its msg argument. msg will be the returned value of the anonymous function. To further illustrate how anonymous and self-invoking functions work, this example uses two arguments in the self-invoking function. It accepts animal1 and animal2 as arguments and it gets those values from the trailing parenthesis which pass 'cat' and 'dog' respectively. And of course 'cat' and 'dog' could be variables, and doAlert could be in a loop zipping through an array of data. Something like this…

function doDisplay(msg) {
   document
.writeln(msg+'<br>');
}

for (i=0; i<10; i++) {
  doDisplay
( (function(num1,num2) { return num1 + ' + ' + num2 + ' = ' + (num1+num2); } ) (i, i+5) );
}  

Here we actually have a loop set up. For ten times, it will call doDisplay and its self-invoking function will set up a string to pass to doDisplay. The trailing parenthesis pass the arguments to the anonymous-function, and these values will be used to set up the string.

Well that pretty much covers anonymous, self-invoking functions, but as you might have suspected you can also do named, self-invoking functions as well! Here's a modification of the example above where the anonymous function has been changed to be a regular function named buildString.

function doDisplay(msg) {
   document
.writeln(msg+'<br>');
}

function buildString(num1, num2) {
   
return num1 + ' + ' + num2 + ' = ' + (num1+num2);
}

for (i=0; i<10; i++) {
  doDisplay
( (buildString) (i, i+5) );
}  

And of course this is just a variation on the more traditional function call…

function doDisplay(msg) {
   document
.writeln(msg+'<br>');
}

function buildString(num1, num2) {
   
return num1 + ' + ' + num2 + ' = ' + (num1+num2);
}

for (i=0; i<10; i++) {
  doDisplay
( buildString(i, i+5) );
}  

Which will work equally well.

Understanding Function Pointers

When you create a function, that function occupies a block of memory. Internally, the only reference it has is its location in Javascript's heap. When you pass a pointer to the function you're passing the pointer to this memory location and NOT the function's name. This is best illustrated in code…

var originalFunction = function () {
   alert
('hello world');
}

var copiedFunction = originalFunction;

var originalFunction = function () {
   alert
('goodbye world');
}

copiedFunction
();   // outputs Hello World.

copiedFunction is a pointer to the hello world function that was initially defined. When we later change the original function to say goodbye world, the copiedFunction retains its pointer to the original function (hello world) while originalFunction is now pointing to a new function block in the heap (goodbye world).

Understanding Function Scope

Because Javascript functions are treated like ordinary variables, they have the same scope and rules as ordinary variables -- which, in javascript, can be a little quirky.

Functions defined at the <script> level are global, accessible to every function, object, array, and sub-function regardless of their location in the code. Variables in Javascript have function scope not block scope which means that -- with one caveat -- variables defined inside a function are private to that function and its sub-functions or children.

var globalFunction = function () {    // global, can be accessed anywhere.
   alert
('hello world');
}

var containerFunction = function() { // global, can be accessed anywhere.

 
var subFunction = function() {    // private--global to containerFunction and its children only.
    alert
("I'm Private");
    globalFunction
();              // We can access global Function here 2 levels deep.
 
}

  globalFunction
();                 // We can access global Function here 1 level deep.
  subFunction
()                     // We can access subFunction inside containerFunction.
}

containerFunction
();                
subFunction
(); // This produces an error because
               
// subfunction() only exists inside containerFunction

The caveat? The caveat is if you declare a new variable without the var keyword inside a function it becomes global in scope. It's a quirk of the language that catches many people unawares.

var globalFunction = function () {    // global, can be accessed anywhere.
   alert
('hello world');
}

var containerFunction = function() { // global, can be accessed anywhere.

  subFunction
= function() {        // global to everything (no var keyword for new variable)
    alert
("I'm Private");
    globalFunction
();               // We can access global Function here 2 levels deep.
 
}

  globalFunction
();                 // We can access global Function here 1 level deep.
  subFunction
()                     // We can access subFunction inside containerFunction.
}

containerFunction
();                
subFunction
(); // This executes because we defined subFunction WITHOUT
               
// the var keyword so it was created with a global scope.

Functions As Objects

All functions in Javascript are objects. This is mostly transparent until you deliberately set out to create object-oriented code.

An Array, in Javascript, is also an object. At its top level an Array is an Array…

var myArray = [];
    myArray
[1] = 1;
    myArray
[2] = 2;
    myArray
[3] = 3;

Underneath myArray is its object, which has properties and methods common to all Arrays and which you can extend upon.

var myArray = [];
    myArray
[1] = 1;
    myArray
[2] = 2;
    myArray
[3] = 3;
    myArray
.one = 'one';
    myArray
.two = 'two';
    myArray
.three='three';
   
for (i=0; i<myArray.length; i++) {
       document
.writeln(myArray[i]);
   
}

Here we load up both the array and the properties of the Array object. When we loop through myArray we're only going to get back [1][2][3] (1,2,3), the properties ('one','two','three') aren't a part of the real array they're properties of its object. But check out the loop statement. We used the .length property to find out how many items were in the array.

So the Array, in Javascript, exists on two levels. At the top we have the Array itself and below that is the object for that Array which we can also use to store data in its properties and create any methods we want in addition to drawing on common methods and properties all Arrays share (like .length, .sort, .concat, etc).

Functions, in Javascript, are like Arrays in that at the top level you have the function itself. It does what you define it to do. But underneath that is the function's object and that object has common properties and methods and those properties and methods can be extended on by you, as needed.

Function Objects and Methods

Every function has the following properties…

  • arguments -- An Array/object containing the arguments passed to the function
    • arguments.length -- Stores the number of arguments in the array
    • arguments.callee -- Pointer to the executing function (allows anonymous functions to recurse).
  • length -- The number of arguments the function was expecting myFunc(x,y) = 2.
  • constructor -- function pointer to the constructor function.
  • prototype -- allows the creation of prototyes.

Every function has the following methods…

  • apply -- A method that lets you more easilly pass function arguments.
  • call -- Allows you to call a function within a different context.
  • toSource -- Returns the source of the function as a string.
  • toString -- Returns the source of the function as a string.
  • valueOf -- Returns the source of the function as a string.

Understanding Function Arguments

The argument list in your function declaration can be considered a recommendation. If you have the following…

var myFunc = function(a,b,c) { }

You can still call the function the following ways…

myFunc(1);
myFunc
(1,2);
myFunc
(1,2,3);
myFunc
(1,2,3,4,5,6,7,8,9,10);

If myFunc(1) is called then b and c will be of type undefined and you can look for this in several ways.

var myFunc = function(a,b,c) {
   
if (!a) {a=1};
   b
= b || 2;
   
if (c==undefined) {c=3;}
   document
.writeln(a+'<br>'+b+'<br>'+c+'<BR>');
}
myFunc
(1);

As you can see, undefined is a false condition so you can use boolean operators to detect if an argument wasn't set and correct it. The safest way to correct for an unsent argument is to look for undefined as this prevents values such as false and null from appearing as an unset variable.

The function .length property and the arguments.length property allow you to check for the expected number of arguments and the number of arguments actually passed. For instance…

var myFunc = function(a,b,c) {
   document
.writeln('expected '+myFunc.length+' arguments.<BR>');  //Outputs: 3
   document
.writeln('got ' + arguments.length+ ' arguments.<BR>'); //Outputs: 1
}
myFunc
(1);

As you can see, the function expected 3 arguments but got only 1!

Mastering the Arguments Array

In our example above a,b, and c represented variables we used to capture the first three arguments. If there weren't enough arguments the variables were set to undefined, if there were too many, well they would still be available to us in the arguments array.

var myFunc = function() {
   document
.writeln(arguments.length+'<BR>');   // displays 10
   
for(i=0; i<arguments.length; i++) {
      document
.writeln(arguments[i]+',');       // displays: 1,2,3,4,5,6,7,8,9,10,
   
}
}
myFunc
(1,2,3,4,5,6,7,8,9,10);

After this article was published Matthew Eernisse (Author of Build Your Own Ajax Web Applications) noted that the arguments property isn't the same as user-defined arrays. While you access the elements the same and .length is the same, the arguments array doesn't have all the methods (sort, splice, slice, etc) that a user-defined array contains.

If you'd like to be able to manipulate the arguments array with the same tools you use to manipulate a normal array, Mr. Earnisse provided a very elegant solution.

var args = Array.prototype.slice.apply(arguments);

With this code args becomes a proper array copy of arguments.

Howto Pass Arguments To Another Function

Every now and again, someone asks how to pass the arguments to another function as arguments instead of an array. For instance…

var otherFunc=function() {
   alert
(arguments.length); // Outputs: 1 -- an array
}
   
var myFunc = function() {
  alert
(arguments.length); // Outputs: 10
  otherFunc
(arguments);
}
myFunc
(1,2,3,4,5,6,7,8,9,10);

Here we pass 10 arguments to myFunc and myFunc tries to pass them to otherFunc but instead we pass it the arguments array and not an arguments list.

We can work around this with a function method called apply which will apply the arguments array as an arguments list in the new function.

var otherFunc = function() {
   alert
(arguments.length); // Outputs: 10
}
   
var myFunc = function() {
  alert
(arguments.length); // Outputs: 10
  otherFunc
.apply(this, arguments);
}
myFunc
(1,2,3,4,5,6,7,8,9,10);

Howto use recursion on Anonymous Functions

Lets say we have an anonymous factorial function and we want to do it recursively. How do we call a function without a name? Well in Javascript the arguments.callee property contains a pointer to the currently executing function which means an anonymous function can, indeed, call itself.

alert((function(n){ if(n <= 1){return 1;}else{return n*arguments.callee(n-1);}})(10));

Conclusion

There's a lot more to functions in regards to object oriented programming and the Javascript Function object, but that is subject matter for another, future, article. For now, however, I hope this article has helped you grasp the power and flexibility Javascript functions afford you in expressing your programming goals.

Kaynak www.hunlock.com/blogs/Functional_Javascript


Yorumunuzu Ekleyin


Yükleniyor...
Yükleniyor...