This course has been updated! We now recommend you take the The Good Parts of JavaScript and the Web course.
Transcript from the "Functions as Subroutines" Lesson
[00:00:00]
>> [MUSIC]
[00:00:04]
>> Douglas Crockford: So the idea of functions in programming languages came from the idea of subroutines, which was some piece of code which could be called from multiple places. And in various languages, at various times, has been called subs, procedures, procs, funcs, functions, and lambdas, but they're all the same idea.
[00:00:27]
>> Douglas Crockford: So why did that occur in programming languages? There are a number of good reasons. The first one was code reuse. It was recognized that there were certain patterns in programs that happened over and over and over again. And being able to isolate those was originally a memory conserving device.
[00:00:47] A more useful reason turns out to be decomposition. Where we can take a complicated problem and break it down into smaller simpler problems, each of those represented as a function or subroutine. Then there was modularity. These things can have interfaces that they can defend, becoming black boxes. And that makes them much easier to reason about.
[00:01:10] It turns out that there is expressive power to subroutines. That essentially you're designing your own language based on a library of things that you can collect and put together. Then finally, we'll get to this in the next talk, higher order functions. Where functions become parameters and functions become return values.
[00:01:29] And that pushes programming to a whole another level. It's quite amazing.
>> Douglas Crockford: One of the things that was discovered around Lisp in 1958 was recursion, where a function can be defined in terms of itself. This is an idea that came from mathematics but is brilliantly implemented in modern programming languages.
[00:01:55]
>> Douglas Crockford: One of the best examples of recursion is the Quicksort. This is the entire algorithm. You divide an array into two groups, low and high. So you start with an array, you pick a point in the middle, everything that's bigger than a value goes to one side, everything smaller goes to the other side.
[00:02:13] And then you call Quicksort again on those two sides. And that's it, that's the entire algorithm, it's amazing. And Quicksort turns out to be a very fast sorting technique. In the average case, it's n log n, which is pretty good for a sort. And it falls out trivially from the definition of recursion.
[00:02:36]
>> Douglas Crockford: But there's a guy named Tennent who wrote a book called The Principles of Programming Languages. And he came up with a principle of correspondence, which showed a correspondence between variables and parameters. And in languages like JavaScript, variables and parameters are the same thing, basically, in that one can be represented as the other.
[00:02:57] And this shows two implementations of factorial that both do exactly the same thing, that both act on something called result. But in one of them, result is a variable, in the other, result is a parameter. The parameter acts as a variable in that it's been created within a function, which is invoked immediately, which provides the initial value for the parameter.
[00:03:23]
>> Douglas Crockford: There are some people who took that idea of being able to take any expression and encapsulate it in a function which is immediately invoked without changing the meaning of the thing. And that may be a useful thing to have in a language, particularly if you have macros or some other thing which wants to act on pieces of program as if they were data.
[00:03:47] And this almost works in JavaScript. You can take almost any expression, or almost any statement, and wrap it in an immediately invoked function. And it almost works, except if the expression contains var or function or break or continue or return, because the meaning of all those change when you put them inside of a function.
[00:04:09] And also, this in arguments also change when you put them inside of a function. But other than that, those two expressions are equivalent.
>> Douglas Crockford: Let's see, closure, wow, how to sneak up on this one.
>> Douglas Crockford: So the original Lisp had static scoping, I'm sorry, all right, let me skip that one.
[00:04:38] Okay, we'll go to the definitions. Okay, closure is one of the really central ideas to JavaScript. And unfortunately, it's called closure, which is bad. It comes from set theory, where one thing can be closed over another thing, which is unfortunate. Cuz most people think of the word closure as meaning retribution or vengeance.
[00:04:57] You're like, I've been wronged and I'm gonna get me some closure. And this isn't anything like that. This has to do with the vitality of the state of a function. So the context of an inner function includes the scope of the outer function. Just because of the rules scoping and nesting.
[00:05:19] The weird thing is that the inner function enjoys their context even after the parent function or the other function has returned. Otherwise, function scope works like block scope. So let me show you an example of what that means, cuz those way too abstract. So here we got a little piece of code.
[00:05:36] We're defining a function called digit_name. And it's gonna take a number, and it's gonna return a string, which is the name of that number. Now, the problem with the way I wrote this is that names is a global variable. Which means if there's anything else in my program called names, then it's gonna conflict with this one.
[00:05:57] And either my program's gonna fail or they're gonna fail, or maybe we'll both fail, and that's bad. And there's no way to test for that in general because you can't predict all of the code your code's ever gonna have to run with. So especially true on the web in ad-supported sites.
[00:06:13] Because there's no way you can anticipate what's going to be in all the ads you're gonna run with. And ads contain some of the worst code you have ever seen. They contain lots of global variables with lots of stupid names. And there's a good chance you're gonna fail on that, and there's no way you can anticipate it.
[00:06:29] So the only defense is to try to reduce your use of global variables as much as possible. So here's another way we can write this function. Now names is a private variable of the digit_name function. And this works. So because it's inside the function scope, it's not visible outside of the function, so it will not conflict with any other global name.
[00:06:52] So that's good. The thing that's wrong with this one is, every time we call digit_name, we're going to have to create an array, put ten things in it only to take one thing out, which is hugely inefficient. Now an optimizing compiler might notice that names is variant over invocations and try to factor that out.
[00:07:11] But JavaScript compilers don't do that and probably never will. Because an optimizing compiler can take many minutes to do its work and the web wants to start right now. So there's never gonna be time to do that kind of optimization. So we have to do that optimization ourselves.
[00:07:29] And it turns out it's easy to do. So I now have a function, and it's got the names variable as before. But now instead of returning a value for names, it's returning a function that's gonna return the value from names. And we're going to invoke it immediately, okay?
[00:07:51] So what's being assigned to digit_name is not this function, it's the result of the function, which is the green function.
>> Douglas Crockford: Now, once we do that assignment, this outer function has returned, it has completed its work, it's done. But then when we call digit_name down here, we get the three, okay?
[00:08:12] So this function continues to enjoy access to that variable even after the function that created that variable has gone. Everybody with me? This is the most important thing I'm gonna tell you all day, so it's really important that everybody be with me on this step.
>> Douglas Crockford: If you don't understand how this function works, you gotta tell me now.
[00:08:38]
>> Douglas Crockford: All right, we all got it? Okay, so let's try this one more time from a different point of view
>> Douglas Crockford: So this is back to the initial problem, we got the global variable and that's bad. But one thing what I can do, is because of the thing we just talked about, where we can take any expression and put it inside of an immediately invoked function, so I can do that.
[00:09:04] I can take the original function and wrap it in another function, I can do that. Cuz it means exactly the same thing. Everybody with me? Okay, so then all I am gonna do is move the names thing inside of there, get the same thing.
>> Douglas Crockford: Everybody still with me?
[00:09:28]
>> Douglas Crockford: Now, there are some people who have proposed a possible optimization, where it involves changing the value of digit_name. So initially, digit_name is the function which will create the names array. And after it's called the first time, it will replace itself with the function that extracts things from that array.
[00:09:51] The theory being that you don't have to pay for the cost of the names until the first time you need to do it. So if it turns out you never call this function, then you never pay that cost. I don't like this for a couple of reasons. The first one is that we're changing the value of that function as we're going.
[00:10:14] That kind of self-modification is confusion and I don't like confusion. But worse than that, we're breaking the first class list of our function. Because I can take my digit_name and I can put it in another object where it's gonna act as a method. And when I call the function from over there, it's gonna go through here, change the value of digit_name, but this guy doesn't see that digit_name changed.
[00:10:39] So it means that now every time I call this function, I'm going to have to pay this overhead because I've broken the first classness of the function. I think this is a really bad form. Now, you could fix this by putting an if statement in it.
>> Douglas Crockford: But it's not worth it.
[00:11:00] So you could ask, why? You might notice that the cost of the statement if you run this function a million times, but if you're gonna run it a million times, you're gonna break other things long before you get to the point where you notice that payoff. And if you're that concerned about optimizing the case where you're running the case a million times, you shouldn't be trying to optimize the case where you do it zero.
[00:11:26] That seems to me a really bad trade-off.
>> Douglas Crockford: So if you look at scopes as sets, it's exactly inside-out of how the functions look. So here I've got an outer yellow function. What I can see from that function is x. But from the inner green function, I can see x and y, okay?
[00:11:53] And you can see that scope is completely enclosed by the other scope. That's where the word enclosure comes from. So here's an example from the browser. It's a fade function. It's going to take some element of the DOM and cycle it through the colors yellow to white in however long that takes.
[00:12:19] And the idea is that there's gonna be something changing on the page and we just want to get the user's attention on it briefly, and we'll do it by color cycling. So we'll start with the fade function. We'll pass in the id of the DOM element that we want to be colorizing.
[00:12:36] We'll go to getElementById to get that DOM node. We'll set our variable level to 1. We'll create a step function. And we'll call setTimeout, passing that step function, telling to call it in a tenth of a second. And then it's finished. Okay, at this point, fade has completed, it's done it's work, it is complete.
[00:13:00] Suddenly, a tenth of a second later, setTimeout causes step to run, okay? So setTimeout goes to his level and changes it to a hex string. It's getting the level of the fade function. Okay, so it's remembering the value that the outer function had. It doesn't have a copy of that variable, it is still bound to that very variable.
[00:13:23] Okay, it will then go to that DOM node, it knows what node it is because its parent looked it up, and it will change its background color. We'll then look again at the level and ask, is it 15 yet? If not, it will change it and call set timeout again.
[00:13:41] Okay, again, it's not changing its copy, it is changing the original variable. So every time it looks at it, it will see the current value of that variable. And it will keep doing until it gets to 15, and then it stops. Now one of the nice things about doing all this in a function, and the way that function's scope work, is I could fade on two DOM elements at the same time.
[00:14:02] Go fade, fade. And those two will work, but they will not interfere with each other. Cuz each call to fade creates it's own DOM and level in step values, which are completely distinct and held inside of their scope from all other invocations. And so there's no way they can conflict, unless maybe you told them to fade on the same guy at the same time.
[00:14:25] In that case, you got trouble. But otherwise, this is a very clean, very elegant way of managing your program.
>> Douglas Crockford: And so a later method, I think I'll skip that. It's kind of redundant for some stuff we're gonna do later. I'll skip that one too, and that one.