Learning JavaScript (2016)
Chapter 13. Functions and the Power of Abstract Thinking
If JavaScript were a Broadway play, functions would be the glittering starlet: they would hog the spotlight and take the last bow to thunderous applause (and some boos, no doubt: you can’t please everyone). We covered the mechanics of functions in Chapter 6, but now we’re going to consider the ways functions can be used, and how they can transform your approach to solving problems.
The very concept of a function is chameleon-like: it takes on different aspects when viewed in different contexts. The first—and simplest—perspective on functions we’re going to consider is that of a code reuse vehicle.
Functions as Subroutines
The idea of subroutines is a very old one, a practical concession to managing complexity. Without subroutines, programming would be a very repetitive affair indeed. Subroutines simply package some bit of repetitive functionality, give it a name, and allow you to execute that bit of functionality at any time simply by referring to that name.
NOTE
Other names for a subroutine are procedure, routine, subprogram, macro, and the very bland and generic callable unit. Note that in JavaScript, we don’t actually use the word subroutine. We just call a function a function (or a method). We’re using the term subroutine here to emphasize this simple use of functions.
Very often, a subroutine is used to package an algorithm, which is simply an understood recipe for performing a given task. Let’s consider the algorithm for determining whether the current date is in a leap year or not:
const year = new Date().getFullYear();
if(year % 4 !== 0) console.log(`${year} is NOT a leap year.`)
else if(year % 100 != 0) console.log(`${year} IS a leap year.`)
else if(year % 400 != 0) console.log(`${year} is NOT a leap year.`)
else console.log(`{$year} IS a leap year`);
Imagine if we had to execute this code 10 times in our program…or even worse, 100 times. Now imagine further that you wanted to change the verbiage of the message that gets printed to the console; you have to find all the instances that use this code and change four strings! This is exactly the problem that subroutines address. In JavaScript, a function can fill this need:
function printLeapYearStatus() {
const year = new Date().getFullYear();
if(year % 4 !== 0) console.log(`${year} is NOT a leap year.`)
else if(year % 100 != 0) console.log(`${year} IS a leap year.`)
else if(year % 400 != 0) console.log(`${year} is NOT a leap year.`)
else console.log(`{$year} IS a leap year`);
}
Now we’ve created a reusable subroutine (function) called printLeapYearStatus. This should all be pretty familiar to us now.
Note the name we chose for the function: printLeapYearStatus. Why didn’t we call it getLeapYearStatus or simply leapYearStatus or leapYear? While those names would be shorter, they miss an important detail: this function simply prints out the current leap year status. Naming functions meaningfully is part science, part art. The name is not for JavaScript: it doesn’t care what name you use. The name is for other people (or you in the future). When you name functions, think carefully about what another person would understand about the function if all they could see was the name. Ideally, you want the name to communicate exactly what the function does. On the flip side, you can be too verbose in the naming of your functions. For example, we might have named this function calculateCurrentLeapYearStatusAndPrintToConsole, but the extra information in this longer name has passed the point of diminishing returns; this is where the art comes in.
Functions as Subroutines That Return a Value
In our previous example, printLeapYearStatus is a subroutine in the usual sense of the word: it just bundles some functionality for convenient reuse, nothing more. This simple use of functions is one that you won’t find yourself using very often—and even less so as your approach to programming problems becomes more sophisticated and abstract. Let’s take the next step in abstract thinking and consider functions as subroutines that return a value.
Our printLeapYearStatus is nice, but as we build out our program, we quickly grow out of logging things to the console. Now we want to use HTML for output, or write to a file, or use the current leap year status in other calculations, and our subroutine isn’t helping with that. However, we still don’t want to go back to spelling out our algorithm every time we want to know whether it’s currently a leap year or not.
Fortunately, it’s easy enough to rewrite (and rename!) our function so that it’s a subroutine that returns a value:
function isCurrentYearLeapYear() {
const year = new Date().getFullYear();
if(year % 4 !== 0) return false;
else if(year % 100 != 0) return true;
else if(year % 400 != 0) return false;
else return true;
}
Now let’s look at some examples of how we might use the return value of our new function:
const daysInMonth =
[31, isCurrentYearLeapYear() ? 29 : 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31];
if(isCurrentYearLeapYear()) console.log('It is a leap year.');
Before we move on, consider what we’ve named this function. It’s very common to name functions that return a boolean (or are designed to be used in a boolean context) with an initial is. We also included the word current in the function name; why? Because in this function, it’s explicitly getting the current date. In other words, this function will return a different value if you run it on December 31, 2016, and then a day later on January 1, 2017.
Functions as…Functions
Now that we’ve covered some of the more obvious ways to think about functions, it’s time to think about functions as…well, functions. If you were a mathematician, you would think of a function as a relation: inputs go in, outputs come out. Every input is related to some output. Functions that adhere to the mathematical definition of the function are sometimes called pure functions by programmers. There are even languages (such as Haskell) that allow only pure functions.
How is a pure function different than the functions we’ve been considering? First of all, a pure function must always return the same output for the same set of inputs. isCurrentYearLeapYear is not a pure function because it will return something different depending on when you call it (one year it may return true, while the next year it may return false). Secondly, a function must not have side effects. That is, invoking the function must not alter the state of the program. In our discussion of functions, we haven’t seen functions with side effects (we are not considering console output to be a side effect). Let’s consider a simple example:
const colors = ['red', 'orange', 'yellow', 'green',
'blue', 'indigo', 'violet'];
let colorIndex = -1;
function getNextRainbowColor() {
if(++colorIndex >= colors.length) colorIndex = 0;
return colors[colorIndex];
}
The function getNextRainbowColor will return a different color each time, cycling through the colors of the rainbow. This function is breaking both of the rules of a pure function: it has a different return value each time for the same input (it has no arguments, so its input is nothing), and it is causing a side effect (the variable colorIndex changes). The variable colorIndex is not part of the function; invoking getNextRainbowColor modifies it, which is a side effect.
Going back to our leap year problem for a moment, how can we take our leap year function and turn it into a pure function? Easy:
function isLeapYear(year) {
if(year % 4 !== 0) return false;
else if(year % 100 != 0) return true;
else if(year % 400 != 0) return false;
else return true;
}
This new function will always return the same output given the same input, and it causes no side effects, making this a pure function.
Our getNextRainbowColor function is a little bit trickier. We can eliminate the side effect by putting the external variables in a closure:
const getNextRainbowColor = (function() {
const colors = ['red', 'orange', 'yellow', 'green',
'blue', 'indigo', 'violet'];
let colorIndex = -1;
return function() {
if(++colorIndex >= colors.length) colorIndex = 0;
return colors[colorIndex];
};
})();
We now have a function that has no side effects, but it still isn’t a pure function because it doesn’t always return the same result for the same input. To fix this problem, we have to carefully consider how we use this function. Chances are, we’re calling it repeatedly—for example, in a browser to change the color of an element every half-second (we will learn more about browser code in Chapter 18):
setInterval(function() {
document.querySelector('.rainbow')
.style['background-color'] = getNextRainbowColor();
}, 500);
This may not look so bad, and certainly the intent is clear: some HTML element with the class rainbow will cycle through the colors of the rainbow. The problem is that if something else calls getNextRainbowColor(), it will interfere with this code! This is the point we should stop and question whether a function with side effects is a good idea. In this case, an iterator would probably be a better choice:
function getRainbowIterator() {
const colors = ['red', 'orange', 'yellow', 'green',
'blue', 'indigo', 'violet'];
let colorIndex = -1;
return {
next() {
if(++colorIndex >= colors.length) colorIndex = 0;
return { value: colors[colorIndex], done: false };
}
};
}
Our function getRainbowIterator is now a pure function: it returns the same thing every time (an iterator), and it has no side effects. We have to use it differently, but it’s much safer:
const rainbowIterator = getRainbowIterator();
setInterval(function() {
document.querySelector('.rainbow')
.style['background-color'] = rainbowIterator.next().value;
}, 500);
You may be thinking that we’ve just kicked the can down the road: isn’t the next() method returning a different value every time? It is, but remember that next() is a method, not a function. It operates in the context of the object to which it belongs, so its behavior is controlled by that object. If we use getRainbowIterator in other parts of our program, they will produce different iterators, which will not interfere with any other iterator.
So What?
Now that we’ve seen three different hats that a function can wear (subroutine, subroutine with return value, and pure function), we pause and ask ourselves “So what?” Why do these distinctions matter?
My goal in this chapter is less to explain the syntax of JavaScript than to get you to think about why. Why do you need functions? Thinking about a function as a subroutine offers one answer for that question: to avoid repeating yourself. Subroutines give you a way to package commonly used functionality—a pretty obvious benefit.
NOTE
Avoiding repetition by packaging code is such a foundational concept that it’s spawned its own acronym: DRY (don’t repeat yourself). While it may be linguistically questionable, you’ll find people using this acronym as an adjective to describe code. “This code here could be more DRY.” If someone tells you that, they are telling you that you are unnecessarily repeating functionality.
Pure functions are a slightly harder sell—and answer the question of “why” in a more abstract fashion. One answer might be “because they make programming more like math!”—an answer that might reasonably prompt a further question: “Why is that a good thing?” A better answer might be “because pure functions make your code easier to test, easier to understand, and more portable.”
Functions that return a different value under different circumstances, or have side effects, are tied to their context. If you have a really useful function that has side effects, for example, and you pluck it out of one program and put it into another, it may not work. Or, worse, it may work 99% of the time, but 1% of the time causes a terrible bug. Any programmer knows that intermittent bugs are the worst kind of bugs: they can go unnoticed for long periods of time, and by the time they’re discovered, locating the offending code is like finding a needle in a haystack.
If you are wondering if I’m suggesting that pure functions are better, I am: you should always prefer pure functions. I say “prefer” because sometimes it’s just plain easier to make a function with side effects. If you’re a beginning programmer, you’ll be tempted to do it quite often. I’m not going to tell you not to, but I am going to challenge you to stop and think if you can find a way to use a pure function instead. Over time, you’ll find yourself naturally gravitating toward pure functions.
Object-oriented programming, which we covered in Chapter 9, offers a paradigm that allows you to use side effects in a controlled and sensible manner, by tightly restricting their scope.
Functions Are Objects
In JavaScript, functions are instances of the Function object. From a practical perspective, this should have no bearing on how you use them; it’s just a tidbit to file away. It is worth mentioning that if you’re trying to identify the type of a variable v, typeof v will return "function" for functions. This is good and sensible, in contrast to what happens if v is an array: it will return "object". The upshot is that you can use typeof v to identify functions. Note, however, that if v is a function, v instanceof Object will be true, so if you are trying to distinguish between functions and other types of objects, you’ll want to test typeof first.
IIFEs and Asynchronous Code
We were introduced to IIFEs (immediately invoked function expressions) in Chapter 6, and we saw that they were a way of creating a closure. Let’s look at an important example (one that we will revisit in Chapter 14) of how IIFEs can help us with asynchronous code.
One of the first uses of IIFEs was to create new variables in new scopes so that asynchronous code would run correctly. Consider the classic example of a timer that starts at 5 seconds and counts down to “go!” (0 seconds). This code uses the built-in function setTimeout, which delays the execution of its first argument (a function) by its second argument (some number of milliseconds). For example, this will print out hello after 1.5 seconds:
setTimeout(function() { console.log("hello"); }, 1500);
Now that we’re armed with that knowledge, here’s our countdown function:
var i;
for(i=5; i>=0; i--) {
setTimeout(function() {
console.log(i===0 ? "go!" : i);
}, (5-i)*1000);
}
Take note that we’re using var instead of let here; we’re having to step back in time to understand why IIFEs were important. If you expect this to print out 5, 4, 3, 2, 1, go!, you will be disappointed. Instead, you will find –1 printed out six times. What’s happening here is that the function being passed to setTimeout is not invoked in the loop: it will be invoked at some point in the future. So the loop will run, and i will start at 5, and eventually reach –1…all before any of the functions are invoked. So by the time the functions are invoked, the value of i is –1.
Even though block-level scope (with let variables) essentially solves this problem, this is still a very important example if you’re new to asynchronous programming. It can be hard to wrap your head around, but it’s critical to understanding asynchronous execution (the subject ofChapter 14).
Before block-scoped variables, the way to solve this problem would have been to use another function. Using an additional function creates a new scope, and the value of i can be “captured” (in a closure) at each step. Consider first using a named function:
function loopBody(i) {
setTimeout(function() {
console.log(i===0 ? "go!" : i);
}, (5-i)*1000);
}
var i;
for(i=5; i>0; i--) {
loopBody(i);
}
At each step in the loop, the function loopBody is invoked. Recall how arguments are passed in JavaScript: by value. So at each step, it’s not the variable i that gets passed into the function, but the value of i. So the first time, the value 5 is passed in, the second time the value 4, and so on. It doesn’t matter that we use the same variable name (i) in both places: we’re essentially creating six different scopes and six independent variables (one for the outer scope, and five for each of the loopBody invocations).
Creating a named function for loops that you are only going to use once can get tedious, though. Enter IIFEs: they essentially create equivalent anonymous functions that are invoked immediately. Here’s how the previous example looks with an IIFE:
var i;
for(i=5; i>0; i--) {
(function(i) {
setTimeout(function() {
console.log(i===0 ? "go!" : i);
}, (5-i)*1000);
})(i);
}
That’s a lot of parentheses! If you think about it, though, the exact same thing is being accomplished: we’re creating a function that takes a single argument, and invokes it for each step in the loop (see Figure 13-1 for a visual explanation).
Figure 13-1. Immediately invoked function expression
Block-scoped variables solve this problem for us without the extra hassle of requiring a function to create a new scope for us. This example is simplified greatly with the use of block-scoped variables:
for(let i=5; i>0; i--) {
setTimeout(function() {
console.log(i===0 ? "go!" : i);
}, (5-i)*1000);
}
Note that we use the let keyword inside the for loop arguments. If we put it outside the for loop, we’d have the same problem as before. Using the let keyword in this way signals to JavaScript that at each step of the loop, there is to be a new, independent copy of the i variable. So when the functions passed to setTimeout execute in the future, they’re each receiving their value from a variable in its own scope.
Function Variables
If you’re new to programming, you might want to pour another cup of coffee and sit up straight: this section is something beginners often struggle with, but is one of the most important concepts to wrap your head around.
It’s easy enough to think of numbers, strings, and even arrays as variables; it leads us to this comfortable idea that a variable is a bit of data (or a collection of data in the case of an array or object). Thinking about variables this way, however, makes it more difficult to realize the full potential of functions, because you can pass a function around just as if it were any other variable. Because functions are active, we don’t think about them as bits of data (which we consider to be passive). And it’s true, a function is active when it’s invoked. Before it’s invoked, however…it’s passive just like any other variable.
Here’s an analogy that might help you. If you go to the supermarket, you can think of fruit as bits of data: 2 bananas, 1 apple, and so on. You decide that you’d like to make a smoothie with your fruit, so you buy a blender as well. A blender is more like a function: it does something (namely, converts fruit into delicious smoothies). But when the blender is in your basket—not plugged in, not actively blending—it is just another item in your cart, and you can do the same things with it as you do with your fruit: move it from the cart to the conveyor belt, pay for it, put it in a shopping bag, take it home, and so on. It’s only when you plug it in and put fruit into it and turn it on that it becomes something different than the fruit.
So a function can be used wherever a variable is used, but what does that mean? In particular, it means you can do the following things:
§ Alias a function by creating a variable that points to it.
§ Put functions in an array (possibly mixed with other types of data).
§ Use functions as properties of an object (see Chapter 9).
§ Pass a function into a function.
§ Return a function from a function.
§ Return a function from a function that itself takes a function as an argument.
Is your head spinning yet? Written out this way, it does seem incredibly abstract, and you might reasonably be wondering “Why on earth would you want to do that?” The fact is, this flexibility is incredibly powerful, and all of these things are done quite frequently.
Let’s start with the most comprehensible item on that list: aliasing a function. Imagine you have a function with an incredibly long name and you want to use it multiple times within a few lines, and it’s getting exhausting typing it, and it results in code that’s very hard to read. Because a function is just a data type like any other, you can create a new variable with a shorter name:
function addThreeSquareAddFiveTakeSquareRoot(x) {
// this is a very silly function, isn't it?
return Math.sqrt(Math.pow(x+3, 2)+5);
}
// before
const answer = (addThreeSquareAddFiveTakeSquareRoot(5) +
addThreeSquareAddFiveTakeSquareRoot(2)) /
addThreeSquareAddFiveTakeSqureRoot(7);
// after
const f = addThreeSquareAddFiveTakeSquareRoot;
const answer = (f(5) + f(2)) / f(7);
Note that in the “after” example, we don’t use parentheses after addThreeSquareAddFiveTakeSquareRoot. If we did, we would be invoking the function, and f—instead of being an alias of addThreeSquareAddFiveTakeSquareRoot—would contain the result of invoking it. Then when we tried to use it like a function (f(5), for example) it would result in an error because f wouldn’t be a function, and you can only invoke functions.
This is a completely contrived example, of course, and something you don’t really see that often. Where it does come up, however, is in namespacing, which is common in Node development (see Chapter 20). For example:
const Money = require('math-money'); // require is a Node function to
// import a library
const oneDollar = Money.Dollar(1);
// or, if we don't want to have to say "Money.Dollar" everywhere:
const Dollar = Money.Dollar;
const twoDollars = Dollar(2);
// note that oneDollar and twoDollars are instances of the same type
In this case, we aren’t so much aliasing Dollar as shortening it from Money.Dollar to simply Dollar, which seems a reasonable enough thing to do.
Now that we’ve done the mental equivalent of stretching before exercising, let’s move on to more vigorous abstract thinking.
Functions in an Array
This is a pattern that hasn’t been used very much historically, but its usage is increasing, and it is extremely useful in certain circumstances. One application is the idea of a pipeline—that is, a set of individual steps we want to do frequently. The advantage of using arrays is that you can modify an array at any time. Need to take a step out? Just remove it from the array. Need to add a step? Just append something to the array.
One example is graphics transformations. If you’re building some kind of visualization software, there is often a “pipeline” of transformations that you apply to many points. The following shows an example of common 2D transforms:
const sin = Math.sin;
const cos = Math.cos;
const theta = Math.PI/4;
const zoom = 2;
const offset = [1, -3];
const pipeline = [
function rotate(p) {
return {
x: p.x * cos(theta) - p.y * sin(theta),
y: p.x * sin(theta) + p.y * cos(theta),
};
},
function scale(p) {
return { x: p.x * zoom, y: p.y * zoom };
},
function translate(p) {
return { x: p.x + offset[0], y: p.y + offset[1]; };
},
];
// pipeline is now an array of functions for a specific 2D transform
// we can now transform a point:
const p = { x: 1, y: 1 };
let p2 = p;
for(let i=0; i<pipeline.length; i++) {
p2 = pipeline[i](p2);
}
// p2 is now p1 rotated 45 degrees (pi/4 radians) around the origin,
// moved 2 units farther from the origin, and translated 1 unit to the
// right and 3 units down
This example is very basic for a graphics transformation, but hopefully it gives you a glimpse into the power of storing functions in an array. Note the syntax as we apply each function in the pipeline: pipeline[i] accesses element i of the pipeline, which evaluates to a function. Then the function is invoked (parentheses). The point is passed in, and then assigned back to itself. In this way, the point is the cumulative result of executing each step in the pipeline.
Pipeline processing is not just found in graphics applications: it’s also popular in audio processing and many scientific and engineering applications. In reality, any time you have a series of functions you need to execute in a specified order, a pipeline is a useful abstraction.
Pass a Function into a Function
We’ve already seen some examples of passing functions to functions: we have passed functions to setTimeout or forEach. Another reason to pass a function to a function is to manage asynchronous programming, a paradigm that’s exploded as asynchronous programming has become more popular. A common way to achieve asynchronous execution is to pass a function (usually called a callback, and often abbreviated cb) into another function. This function is invoked (called) when the enclosing function is done with whatever it’s doing. We will be discussing callbacks extensively in Chapter 14.
Callbacks aren’t the only reason you might want to pass a function into another function; it’s also a great way to “inject” functionality. Let’s consider a function called sum that simply sums all of the numbers in an array (for the sake of simplicity, we won’t do any checking or error handling if the array contains elements other than numbers). This is an easy enough exercise, but what if we then need a function that returns the sum of squares? We could, of course, simply write a new function called sumOfSquares…but what happens when we need the sum of cubes? This is where the ability to pass a function can be very helpful. Consider this implementation of sum:
function sum(arr, f) {
// if no function is supplied, use a "null function" that
// simply returns its argument unmodified
if(typeof f != 'function') f = x => x;
return arr.reduce((a, x) => a += f(x), 0);
}
sum([1, 2, 3]); // returns 6
sum([1, 2, 3], x => x*x); // returns 14
sum([1, 2, 3], x => Math.pow(x, 3)); // returns 36
By passing an arbitrary function into sum, we can make it do…well, anything we want. Need the sum of square roots? No problem. Need the sum of the numbers taken to the 4.233 power? Simple. Note that we want to be able to call sum without doing anything special…meaning there is no function. Inside the function, the parameter f will have the value undefined, and if we tried to invoke it, it would cause an error. To prevent this, we turn anything that isn’t a function into the “null function,” which, in essence, does nothing. That is, if you pass it 5, it returns 5, and so on. There are more efficient ways we could have handled this situation (such as invoking a different function without the overhead of invoking the null function on every element), but it’s good practice to see “safe” functions created this way.
Return a Function from a Function
Returning a function from a function is perhaps the most esoteric usage for functions, but is extremely useful. You can think of returning a function from a function like a 3D printer: it’s a thing that does something (like a function) that can, in turn, make something that also does something. And the exciting part is that the function you get back can be customized—much like you can customize what you print from a 3D printer.
Let’s consider our sum function from earlier that takes an optional function to operate on each element before summing it. Remember how we said we could create a separate function called sumOfSquares if we wanted to? Let’s consider the situation in which we need such a function. Specifically, a function that takes an array and a function is not good enough: we explicitly need a function that takes only an array and returns the sum of squares. (If you are wondering when such a circumstance might arise, consider an API that allows you to provide a sum function, but only accepts functions that take a single argument.)
One approach would be to create a new function that simply calls our old function:
function sumOfSquares(arr) {
return sum(arr, x => x*x);
}
While this approach is fine, and may work if all we need is the one function, what if we need to be able to repeat this pattern over and over? A solution to our problem might be creating a function that returns a specialized function:
function newSummer(f) {
return arr => sum(arr, f);
}
This new function—newSummer—creates a brand new sum function that has only one argument, but uses a custom function. Let’s see how we might use it to get different kinds of summers:
const sumOfSquares = newSummer(x => x*x);
const sumOfCubes = newSummer(x => Math.pow(x, 3));
sumOfSquares([1, 2, 3]); // returns 14
sumOfCubes([1, 2, 3]); // returns 36
NOTE
This technique—where we have taken a function with multiple arguments and converted it to a function with a single argument—is called currying, after its developer, American mathematician Haskell Curry.
The applications for returning a function from a function are often deep and complicated. If you want to see more examples of this, look at middleware packages for Express or Koa (popular JavaScript web development frameworks); more often than not, middleware is a function that returns a function.
Recursion
Another common and important way that functions are used is recursion. Recursion refers to a function that calls itself. This is a particularly powerful technique when the function does the same thing with progressively smaller sets of input.
Let’s start with a contrived example: finding a needle in a haystack. If you were confronted with a real-life haystack and had to find a needle in it, a viable approach might be this:
1. If you can see the needle in the haystack, go to step 3.
2. Remove a piece of hay from the haystack. Go to step 1.
3. Done!
Basically you’re whittling the haystack down until you find the needle; in essence, this is recursion. Let’s see how we would translate this example into code:
function findNeedle(haystack) {
if(haystack.length === 0) return "no haystack here!";
if(haystack.shift() === 'needle') return "found it!"
return findNeedle(haystack); // haystack has one fewer element
}
findNeedle(['hay', 'hay', 'hay', 'hay', 'needle', 'hay', 'hay']);
The important thing to note in this recursive function is that it is handling all the possibilities: either haystack is empty (in which case there’s nothing to look for), the needle is the first element in the array (done!), or it’s not (it’s somewhere in the rest of the array, so we remove the first element and repeat the function—remember that Array.prototype.shift removes the first element from the array in place).
It’s important that a recursive function has a stopping condition; without it, it will keep recursing until the JavaScript interpreter decides the call stack is too deep (which will cause your program to crash). In our findNeedle function, we have two stopping conditions: we stop because we found the needle, or we stop because there is no haystack. Because we’re reducing the size of the haystack every time, it’s inevitable that we will eventually reach one of these stopping conditions.
Let’s consider a more useful, time-honored example: finding the factorial of a number. The factorial of a number is the number multiplied by every number up to it and is denoted by an exclamation point after a number. So would be . Here’s how we would implement this as a recursive function:
function fact(n) {
if(n === 1) return 1;
return n * fact(n-1);
}
Here we have a stopping condition (n === 1), and every time we make the recursive call, we reduce the number n by one. So eventually we’ll get to 1 (this function will fail if you pass it 0 or a negative number, though of course we could add some error conditions to prevent that from happening).
Conclusion
If you have experience with other functional languages—like ML, Haskell, Clojure, or F#—this chapter was probably a cakewalk for you. If not, it probably stretched your mind a bit, and you might be reeling a little from the abstract possibility of functional programming (believe me, when I first encountered these ideas, I certainly was). You might be a little overwhelmed by the various ways you can accomplish the same thing, and wondering which way is “better.” I’m afraid there’s no easy answer to that question. Often it depends on the problem at hand: some problems strongly suggest a certain technique. A lot also depends on you: what techniques resonate with you? If you find the techniques presented in this chapter bewildering, I encourage you to reread it a few times. The concepts here are extremely powerful, and the only way you’ll know if they’re useful techniques for you is if you take some time to understand them.