Functional Programming
Programming with functions! Weren't we already doing that? Well yes, but we can use functions more heavily, especially in place of loops.
Objectives
- Use callbacks with setInterval and setTimeout
- Understand how functions can be passed in and out of other functions
- Compare and contrast the four main JavaScript iterators: forEach, map, reduce, filter
- Utilize callbacks with JavaScript iterators
Previously, we saw that functions can be assigned to variables. For example:
var add = function(a, b) {
return a + b
}
add(1, 2);
> 3
Functions are first-class citizens in JavaScript. This means that we can create functions, store them into variables, and pass functions into other functions. Functions are only executed when called. Try this to illustrate:
Exercise
Try running the following in Node. What is printed to the screen?
var bag = function() {
console.log('Hello, I am a bag');
}
console.log(bag);
We can take advantage of this behavior by defining callback functions. Callback functions are passed via variable name (reference), and are called at a specific time.
Do something later: Callbacks
The setTimeout()
function takes a function and a delay in
milliseconds, and executes the function as soon as possible after that
delay has passed.
var announce = function() {
console.log('Ding!');
}
var threeSecondTimeout = setTimeout(announce, 3000);
This can be done via anonymous functions as well. Anonymous functions are functions that are not stored to a variable. They are great for functions you only need to define once. Here's an example.
var fiveSecondTimeout = setTimeout(function() {
console.log('Ding!');
}, 5000);
The setInterval()
function takes a function and a delay in
milliseconds, and executes that function as closely as possible each
time that interval of milliseconds has passed.
function annoy() {
console.log('Are we there yet?');
}
var oneSecondInterval = setInterval(annoy, 1000);
Things to be careful of: you need to know what the function expects as parameters. Javascript is forgiving, but not a mind-reader.
Oh, and if you want to disable the timers before they fire, you can use the clearTimeout(timeoutHandle)
or clearInterval(intervalHandle)
functions:
var fourSecondTimeout = setTimeout(announce, 4000);
clearTimeout(fourSecondTimeout);
clearInterval(threeSecondInterval);
clearInterval(fiveSecondInterval);
Returning functions from functions
You can probably guess by now how to return a function from a function:
var drinkMaker = function(drinkType) {
return function(drinkVolume) {
return {
drink: drinkType,
volume: drinkVolume
};
};
};
var cocktailMaker = drinkMaker('cocktail');
var smoothieMaker = drinkMaker('smoothie');
cocktailMaker('4oz');
smoothieMaker('16oz');
We'll see that if we print the contents of cocktailMaker
and smoothieMaker
, we get functions that were returned from the function drinkMaker
. Now, we can call these functions and pass in additional arguments. Neat!
Functional programming is a popular topic in the context of Node, so we'll be seeing more examples like these in the future. Like with iterators!
Iterators
Iterators are examples of functional programming replacements for for
loops. We can use these functions to perform common Array operations for us.
What might we want to with an array?
- run some piece of logic on each item
- create a new array with each item being slightly transformed
- filter the array to contain only a subset of items
- combine all the items in some fashion
We could accomplish all of this using for
loops, but writing for
loops is error prone and tiring. JavaScript provides iterator functions for all of these common operations. They are called (respectively):
General Practice
- Declare an array
- Call an iterator on the array
- Pass a function to the iterator
- Get results
forEach
forEach
is the functional programming replacement for your standard for
loop. You can take the body from your for
loop, wrap it in a function, and pass that argument to forEach
. Let's look at an example:
var friends = ["Markus", "Tim", "Ilias", "Elie"];
// old way, with a for loop
for (var i = 0; i < friends.length; i++) {
console.log("Hello, " + friends[i] + "!");
}
// cool new way, with the .forEach iterator
friends.forEach(function (buddy) {
console.log("Hello, " + buddy + "!");
});
// both output the same thing
// > Hello, Markus!
// > Hello, Tim!
// > Hello, Ilias!
// > Hello, Elie!
Try it
Use the .forEach
iterator to loop over the following
array of foods and say you like them.
var foods = ["pizza", "tacos", "ice cream"];
// your code here
// The output should be
// > "I like pizza"
// > "I like tacos"
// > "I like ice cream"
Try it again
Use the .forEach
iterator to loop over the following
array of objects and say how delicious each one is.
var foods = [
{name: "Pizza", level: "very"},
{name: "Tacos", level: "mostly"},
{name: "Cottage Cheese", level: "not very"}
];
// your code here
// The output should be
// > Pizza is very delicious
// > Tacos is mostly delicious
// > Cottage Cheese is not very delicious
map
Sometimes we want to loop over an array and build a new array in the
process. This is what map
helps us solve. It is like forEach
, but
it returns the new array that is created.
var names = ["tim", "ilias", "elie", "markus"];
// old way with for loop
var cased = [];
for (var i = 0; i < names.length; i++) {
cased.push(names[i].toUpperCase());
}
console.log(cased);
// new way with `map`
var cased = names.map(function (person) {
return person.toUpperCase();
});
console.log(cased);
// Should output
// > ["TIM", "ILIAS", "ELIE", "MARKUS"]
// > ["TIM", "ILIAS", "ELIE", "MARKUS"]
filter
Filter is an iterator that loops through your array and filters it down to a subset of the original array. A callback is called on each element of the original array: if it returns true, then the element is included in the new array, otherwise it is excluded.
var names = ["tim", "ilias", "elie", "markus"];
var isEven = function (name) {
return name.length % 2 === 0;
};
var isOdd = function (name) {
return name.length % 2 !== 0;
};
var evenLengthNames = names.filter(isEven);
var oddLengthNames = names.filter(isOdd);
console.log(evenLengthNames);
console.log(oddLengthNames);
// Should output
// > ["elie", "markus"]
// > ["tim", "ilias"]
reduce
Reduce iterates over an array and turns it into one, accumulated
value. In some other languages it is called fold
.
var nums = [1,2,3,4];
var add = function (a, b) {
return a + b;
};
var sum = nums.reduce(add);
console.log(sum);
// Should output:
// > 10
// which is, 1 + 2 + 3 + 4
Reduce also usually accepts an option third parameter that will be the initial accumulated value. If it is omitted, then the reduction starts with the first two values in the array.
var nums = [1,2,3,4];
var add = function (a, b) {
return a + b;
};
var sum = nums.reduce(add, 10);
console.log(sum);
// Should output:
// > 20
// which is, 10 + 1 + 2 + 3 + 4
Resources
Here are some good blog posts that break down map
, filter
, and reduce
.