Homework Check
Last time we introduced the Prototype library. Hopefully by now you've at least looked over the Prototype API and are semi-comfortable with Firebug. If not, stop and take a few minutes to look around and write a short script or two.Functional Programming
Functional programming is one of those things we don't really do in Java, since Java lacks first class functions. Essentially, when you can pass functions around like any other variable, you often end up sticking them together ("composing" them) to create new functions. The result of one function gets fed into another, etc. Programming effectively in JavaScript requires recognizing that many patterns that are explicit in Java are implicit in a functional language like JavaScript.There are several 'classic' functions that are usually the basis of any good functional programming library:
- map/collect
- A map function takes a value and returns a new value. It's complement is reduce/inject. Here's an example using Prototype's collect function:
var foo = [1,2,3,4,5];
var bar = foo.collect(function(a) {
return a * 2;
}); // bar = [2,4,6,8,10] - reduce/inject
- Takes two values (at least one is usually a collection, but they both can be) and combines them into one value. When called inject, the first value is usually the 'accumulator' and the second is a singular value. With Prototype, you'll use map/collect and inject on collections, but that's not the only way it is used. Map-Reduce is what powers Google searches. Here's an example:
var foo = [5,4,3,2,1];
var bar = foo.inject(1,function(a,b) {
return a * b;
}); // bar == 120
A little explanation for this one. The first call, the parameters are (1,5), the next call is (5,4), then next is (20,3), etc. The above is how inject works in Prototype, other versions of reduce can pass two outputs of reduce instead of the previous output and the next input. e.g., it might do (1,5), then (4,3), (2,1), resulting in 5,12,and 2, then apply the reduce function on the results and so on until we have a single value. To see how map/reduce powers Google, read Can Your Programming Language Do This? - select/findAll
- Used to select/find a subset of a collection. e.g.,
[1,2,3,4,5].select(function(a) {
return a % 2 == 0;
}); // = [2,4] - all and any
- Returns true if any or all of the elements in a collection match a given predicate. e.g.,
[1,2,3,4,5].any(function(a) {
return a == 4;
}); // == true
With Prototype, if you do not pass a predicate, then the value is evaluated as a boolean. You could use this to detect if a collection contains all (or any) non-zero, non-false and non-empty string values.
Manipulating Functions in JavaScript
Before we talk about design patterns, we need to quick take a look under the JavaScript hood and see how we can manipulate the scope and arguments of functions. Remember that when we call a function as a member of an object, the this
keyword lets us access variables in the scope of that object. But if you have only a reference to a function and you try to call it, this
will refer to the global (window) scope. To allow us to set the scope of a function, we can use call
. Here's an example:var foo = { a: 1};This would be almost equivalent to
function aPlus(b) {
this.a = this.a + b;
return this.a;
}
aPlus.call(foo,5); // == 6 && foo.a == 6
foo.aPlus = aPlus;However, the second example will overwrite
foo.aPlus(5);
aPlus
on foo
. If aplus
happens to be an existing variable or method, we've now overwritten it.
call
can be used whenever you know the exact number of arguments you'll be passing to a function, but what if you don't? You might say, "When would that happen?" What if you have a function that takes a function as an argument, and returns another function? This is something we do a lot in functional programming, so we need a way to keep our function producing functions DRY.Before we discuss how you would call a function without knowing the number of arguments, we need to talk about a special variable:
arguments
. Inside any function, the arguments
variable is automatically defined. arguments
is an array-like structure that has all the arguments passed to the function. In JavaScript, the parameters you declare fo a function are just convenient labels for indecies into arguments
. The JavaScript interpreter doesn't care if you call the function foo(a,b)
with 1,2, or 7 arguments. a = arguments[0]
and b = arguments[1]
, and if arguments[1]
is undefined, so is b
. Like an array, arguments
has a length, so you can iterate over it. e.g.,function sum() {Suppose we also have this function:
var sum = 0;
for(var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
function double(a) {and we want to create a function that takes the sum of 2 or more numbers and returns twice the result. To reuse our existing functions, we need some way of creating a function that passes all of it's arguments to
return a * 2;
}
sum
regardless of the number of arguments passed to it. apply
to the rescue! Like call
, apply
lets you call a function and set the value of this
, but instead of taking the arguments from the 2nd argument on, it takes an array (or array-like object) as the second parameter. e.g.,function doubleSum() {We haven't made use of Prototype here yet because we haven't needed it. However, you may have noticed that we keep referring to
return double( sum.apply(this,arguments) );
}
doubleSum(1,1,1,1,1); // == 10
doubleSum(1,2,3); // == 12
doubleSum(); // == 0
arguments
as an "array-like" object. That's because it's not a real Array and doesn't have any of the instance methods you can expect an array to have. Prototype provides a utility function for just this purpose, $A
. $A(arguments)
returns a true array with all of the arguments in it, so you can call all the standard Array methods and Prototype methods on it. We can rewrite sum
like so:function sum() {
return $A(arguments).inject(0,function(a,b) {
return a + b;
});
}
Using apply we can even write call
:function myCall(func,scope) {That covers
var args = $A(arguments).slice(2); // creates a copy of arguments from index 2 on
return func.apply(scope,args);
};
call
and apply
. Next time we'll see how we can use what we now know about functional programming and JavaScript mechanics to implement the design patterns we know and love from Java.