[MUSIC] Okay, now in our study of lexical scope, we can get to the motivation of why this is the symantecs you want. So remember lexical scope is using the environment where a function is defined to look up variables in the function body. The other natural thing you might think of, go, does have a name, it's called dynamic scope. If you have dynamic scope use the environment where the function is called. So decades ago, these were considered reasonable options and in a programming language course, you would study both and think about which one of language you might have. I think it's fair to say that, these days, lexical scope is considered the one that makes sense. We'll talk a little bit at the end of this segment about things that do work with dynamic scope, it is occasionally useful. But lexical scope is really how programming languages work these days. So, there are reasons for this and these are precise technical reasons. Sometimes in programming languages, things are just a matter of taste, but I'm about to show you things that lexical scope let's you do, ways it let's you reason about code and dynamic scope simply does not, and that much is not a matter of opinion. The only opinion you might have is whether or not those reasons are good or important. So let's just get started. I'll show you three main reasons. The first two are actually related. The first one I'll say that the meaning of a function does not depend on what variable names you use. This is nice, because you can go in and change the names of your variables, say to make them more informative or to port them to different human language or something, and you can know that it's not going to affects any of your callers. So for example, look at this first function we see here we've seen a function like this before, and suppose that you come along and say, you know, x is not very, a very good name for that. I want to call it q of fu or pizza instead. You could change it here and you could change it here and you would know that no caller can tell. It's a local variable and the name of local variables should really never matter. It's good abstraction. It's good modularity. It's good software engineering and it's only lexical scope that gives us that. Let me show you that with the code here. So here, I have the function that we had on the slide, I've just called it f1 to distinguish it from f2 here. And f2, I've done the variable renaming I suggested. It's exactly like f1, except I've used q for my local variable both in its definition and its use instead of x. And it turns out that no one will ever be able to tell. If I call (f1 7) and get a function back, that function is going to add 15 to its argument, 2y + 1. If I call (f2 7), I will get back a function that adds 15 to its argument. All callers can tell is that I get back a function that adds 15 to its argument, so both a1 and a2 will be 19. It doesn't matter that there is an x in scope here. If we had dynamic scope, if we evaluated these function bodies, x + y + z, q + y + z, where we called these functions instead of where they were defined, then under dynamic scop, one of these functions would end up using this 17 and the other one would actually end up with an unidentified variable. because we would try to use q and there would not be any q here, that's actually one of the other reasons we're going to get to, okay? So that's our first reason is that function domain does not depend on the variable name uses. I actually already showed you another example of this which is the second example down here, that it lets you remove unused variables. In the previous segment, I showed you an example where we had a function body that was let val x = 3 or something like that, in g called 2. This val x is irrelevant, so it would be fine to remove it. On the other hand, if we had dynamic scope, then if the caller to f passed in some g that had in its function body x, we might end up using that x, because here is the call site and under dynamic scope, which is not what ML has. We would end up using the environment where the function was called. So you really need lexical scope, to reason about these functions this way, this way, and this is essential, for modularity of the software. And modularity, in software is one of the most important features you want from your programming language. Okay. On to reason two, somewhat similar. Functions can be type checked and reasoned about where they're defined not where they're used. So let's look at this example. we can just look at it on this slide here. I look at this function f, which we, it's the same one I've been showing you. And we can reason about the fact that it returns 2y + 1 and its function that when called returned 2y + 1, excuse me. And its type is take an int and return an int arrow int. So its type is int arrow, int arrow int. Once we know that it type checks, we know that if we call it with an int, that shouldn't lead to any problems. And indeed, when we call it was 7 here on the second to last line, we get back the function that will add 15 to its argument and then when we call that with 4 we get 19. What if we had dynamic scope? If we had dynamic scope, then down here at the bottom, when we end up calling this function, we go to evaluate x + y + z. What we're going to get back, hm, we're going to look up x in our environment, we're going to get a string. That's a really bad idea since you can't add strings in ML. We're going to look up y in the environment. We're going to get an undefined variable. Well, no, y we might get, or z because there is no y and z here. Those are undefined variables. So our whole purpose of type checking just got thrown out the window with dynamic scope, because we end up using the wrong variables in the wrong places. Okay? Onto our third and probably most exciting example, because it's going to to let us do new things. Closures just got much more powerful, because we can use this lexical scope idea to have them store whatever data they need. And this is, works particularly well with functions like map and filter that I've showed you before that iterate over data structures. So, I have the same code over here. Let me get to it here. Alright. So, we have seen the function filter before. This is just a function that takes a function f and a list xs and returns a list that only contains the elements of xs for which f returns true. Okay? So suppose that I wanted to filter on all of the, I wanted all the numbers that were greater than negative one or the nonnegative numbers. Here is the way I could implement that in these two lines using filter. What I'm going to do is I am going to define a little high order function here it takes in a number x and it returns the function that takes a y and returns true if y > x. So greater than x has type int arrow int arrow int. So, down here where I call greater than x with minus one, I'll get back an int arrow int. Okay? So if I call filter with the result of this call, that's essentially going to be a function that takes in a y And asks is y > -1? Filter that over the list, I get a function that takes a list and returns all the nonnegative numbers in the list, zero or positive. So where are we using lexical, lexical scope here? In the creation of this function that would pass the filter, greater than x, when called with minus one, returns a closure that captures, stores in its environment that x is minus one. So, when we pass this function into filter, we will be asking for each element of the list, is that element greater than minus one? It does not matter that up here in the body of filter, there's a different x. If we had dynamic scope, this would not this, would ask is a number greater than itself, we would get false, and we had filter everything out of our list. What's going on here in general is that filter just says give me a function, any function f, and it does not care about the fact that the closure pass then, can have whatever data it needs in its environment. In this example, it's using just minus one that was stored when we created the closure here. I may show you a second example using an unanimous function. We also call filter. This is a function all greater than or all greater, that takes in a list and a number and filter out all the numbers less than n, less than or equal to n. So it only returns strings strictly greater than n. So, I call filter, I call it with this little anonymous function n, xs, and notice that when I pass in this function, it's going to end up getting called in the body of filter between if and then. But we look up variables in the body of that function, not where the function is called but where the function is defined. So when we look up this n, we will always get the n that was passed into all greater. So this will work correctly if you call a greater with a list and 17, we will filter out anything that is 17 or less and we need lexical scope to do that. Alright? Let's go back to the slides we will see more examples of this after I show you some other high-order functions, but this is really where we get a lot of the power of closures. And now, let me ask the question, if dynamic scope is such a bad idea, why do we even have a name for it? Okay? So, people now do realize that lexical scope for variables is definitely the right default. It's very common behavior across programming languages, it's important when designing a language to get this right. Now, dynamic scope is occasionally convenient in some situations. And so some languages, for example Racket which we'll use later in the course have special ways to do it. You can add to your language a special kind of variable, separate from the regular variables, that works with dynamic scope. Most languages don't bother but it can be convenient. an example is trying to change through dynamic scoping where output is redirected to certain files and things. I'm not going to show an example here. The other thing I would like to point out is if you squint a little bit and think about it, exceptions, which we saw previously in the course, act a little bit like dynamic scope. If you think about a handle expression and when you raise an exception, which handler does it go to? It's not lexical. You don't look at the structure of the code. It's not the closest handle expression where the raise was defined. It's the closest handle on the call stack in the current dynamic nesting of calls and that is much more like dynamic scope. You look up the current handler, is it, did your caller want to handle this exception? Did its callers want to, and it's not based on where things were defined. And lots of experience with exception handling just suggests that for exceptions, that's a better default, people find that more convenient and better stylistically when designing their software.