 Good afternoon, happy Wednesday? Not a lot of nods in the audience. I don't know if that helps you, but it's Monday. Extra nine. So actually just right before we started getting this e-mail from Eric, it looks like your midterms are all graded, so expect those to be posted probably later today or something else. We have to figure that out. I know I can literally just, he sent me an e-mail saying midterm two is great, that's all right. That's good news, right? Probably later tonight, we will send out practice midterms, so that depends on how far we get through today. You're going to ask questions to delay what we get for today? Yes. Is the midterm next Wednesday? No, it's on Monday. Oh, it'll be your Wednesday. No, it won't on Wednesday, right? Before break. Wait, what? No, no, the Wednesday after break. It's on the 21st, right? Which is next Monday? Yeah. It'll be your next Monday. You and the people who are here visually. Have you had any questions? That will be completely erased on the practice midterm. I got to see exactly what we covered, how much we got. We may have to pull back from some other areas. Maybe you didn't really know, and I know that was one that did not do so hot on the midterms. No argument added, so to make you practice it more. I'm going to test you on stuff that you already know how to do well. Any other questions? Combinators. Again, they use all the numbers that they take in. Yes. So the point of combinators, as we'll see, is they do not refer to any global variables, so we can reason about them just looking at them again. So we'll see there's all different types of combinators. We'll see combinators for true, false, and or nots additions. So we're going to build up all of this complex logic using combinators. And there's a fun bit of stuff later that we'll get to. Let's get into it. So we talked about, on Monday, we talked about alpha equivalents. And so what was the idea behind alpha equivalents? Why are we talking about alpha equivalents? So basically, if you have a piece of software, some source code that compiles and runs, it doesn't matter if you go through and change all of your identifier names to something else. I mean, you can change your variable names to whatever random crap or change them all to one unique character and the program is still running in the file and behaving exactly the same way. So the alpha equivalents is like the same thing in our lend-of function extraction thingy. We just replace all the meta variables. Yeah, so basically, and you actually have this functionality, I believe. We get these when you do Java. They clip still. Right? Yes. You know something else. I don't know. I actually don't know what you've done in the QC Max. But in the IDE, right, you can regulate on a parameter name and say a rename. Right? And rename this parameter name to whatever you want. The function is still the same. And how does it do that? Well, it identifies every instance of that parameter usage in the function. Right? And it deals with function shadowing, just like stoking rules that we talked about. It's able to detect that and do that. So it's a very similar thing here. Right? That function, even though you've renamed the parameter names, it's still the same function. It still computes the same thing. Right? And it's kind of an interesting idea when you think about it. The names that we get variables don't actually matter to computation. Right? But what do they matter for? Readability and maintenance. Right? So you've got to think, readability is not only for, well, your coworkers when you start working on a large software team. If somebody has to come fix a bug in your code and you have one letter of variable names all over the place, I guarantee you you'll get very angry emails. And the other thing that you don't really think about, because you usually develop software over the course of a semester and then throw it away or four or five weeks and throw it away and then go to the next thing, is you have to worry about yourself in a year or a year and a half going back to this crappy code that you wrote and being like, oh my gosh, what did I do? And I personally have experienced this in the last two days just trying to debug the code I wrote over the summer that I have tried to do better over time. You still read some code and you're like, I have no idea what this does. The main point of that is variable names are not important for computation but are important to software development. So you need to keep that in mind. So algebraic equivalence is what we're calling this notion that two lambda expressions are the same if they vary only in the names of bound variables. So we know two functions are the same if they're identical strings of characters. We'd say, of course, these are the same functions. But here we're taking that notion up a little bit more. And we're going to use this equal sign of the alpha below to say that expression one is alpha equivalent to expression two. And so to do this, we need some way to say, okay, how can we do this remaining operator? We don't have any principled way of saying, hey, remain this ID to something else. And so how do we want to do this? So you want to define some kind of operator to do this remaining. How are you going to do it? When you're doing your code and your IDE, can you just do a control f and replace all instances of x with foobar? Well, so yeah, x could be a sub part, right? So you have to deal with that. But let's say you do like space x space, replace that with space foobar space. Is that going to work? Project four, I wanted to get that done very quickly. And so if I had code that was repeated but would take too much mental energy to abstract it into a function, we'll just copy this function and modify it a little bit and name it something else. So I have a lot of looped iterator variables like II probably appeared 300 times in that code. And so, and they're all different functions and they're all different scopes. Right, so it may mess with, depending on how your search goes, you may end up colliding with another global variable. There may be another global variable as far as I've already declared, right? And so, yeah, so just maybe a global find replace. I don't know, maybe not be the best idea, but yeah, maybe some of you have found that by doing that, you end up shooting yourself in the foot later when a bug that you've made in one version of that loop now is in n different places throughout your code and you have to fix it in every different place. I know some of you have done that because we've talked about it in my office. Cool. So, for instance, in this landing expression, can we just replace x with foobar? So there's two x's found in two of our variables. Can we still rename all those x's to foob? Yes. Why? Could we change them all to y's? No, why? Why is found in five and a second? Yes, because y is bound in here. We can rename it to foob. We can rename all these x's to foob, right? And that's fine because foob doesn't exist anywhere. It's not a bound variable and it's not a free variable, right? And when we say find replace, we're going to find replace all the meta variable names and all the instances we find of x. And so we can rename y to bar, but we can't do something like rename just y to x, right, as we saw. We can't just rename x to y because that's going to actually change the semantics of our function. Could we rename x to z? All blindly, find and replace all instances of x and replace them with z. What did the output go in? What x was. Right, so the z on the far right, if we change every instance of x, so we change this meta variable from x to z. Now this z is no longer a free variable, it's bound to this x. The effect there being that if we were going to do an apply of that expression and we passed it a 3, 2, full of 1, 2, and 3, the 1 would get used twice instead of the 3 getting used because x and z would be the same. Yes. Exactly. So instead of this variable referring to a global variable and now refers to a parameter where you didn't intend for that to happen. Cool. So we're going to start off by defining a very simple, remaining operation. And then we're going to do these more complicated things of, well we'll see in a second, I don't want to get too far. So we're going to use this operator to say that we are going to replace x with y. That's going to be the syntax. So we have our expression e, our lambda expression. We're going to use the curly braces and we're going to say we're going to replace all instances of x with y. It's necessary if you have a convention to just never reuse identifier names. Say that, why would you not reuse identifier names? Well, because earlier you were like in my bad project core code example. It's not bad. It's not bad. You're right. You're guaranteed that all of my identifiers and all of my stopes were unique. I wouldn't need to do renaming. Maybe. We're going to define absolute equivalence in terms of this renaming operation as we'll see. And in terms of other operators that we'll see and we're going to build this operator on top. We're going to build a substitution operator on top of this and then the substitution operator is going to allow us to do computation. So that's where our eventual goal is going. Yeah. So just to clarify. Let's look at the next line. So the first rule is we have an expression of the form x and if we are replacing x with y, then that is going to return y. So right now we're defining all of our cases. We're trying to do this pretty formally. So we need to exhaustively say for every different type of land expression, how does this operator propagate and what does it return? So if it's not equal to x and we're not interested in it, do nothing. That doesn't change the expression. So if the expression is some ID, z, and we're trying to replace x with y, then return z. So in these two lines, we've covered every possible case where it's a single ID of a land expression. So there's only two cases. It's either equal to x or it's not equal to x. And each of these lines tells you exactly what to do in that case. And in the case of where we have function application, we have e1 space e2. We just propagate and we're trying to replace x with y. We just distribute that essentially to both sides. We're trying to replace all instances of x with y. If we have a lambda expression, we have three cases essentially ignoring the parentheses. We have a land expression can either be an ID and we've covered that in these top two cases. A land expression can either be an application which we've covered in this third case. So the fourth case is what we do if it's an abstraction. And there we have two scenarios just like in the ID case. Either the meta variable is the variable that we're interested in. It's actually x. So what do we do? We change the meta variable. We change that from lambda x to lambda y. And then we're going to propagate our lambda y. We're going to propagate our remaining operation into the body of the abstraction. All this simply says is if you keep propagating this symbol to find this remaining operation and if you see a meta variable of that name, you rename it. And then you go and rename the body. So what's the other case? So we've said it's an ID. It's an application. It's an abstraction. So if the meta variable is not equal, then you don't change the meta variable. And you just rename inside the body of the abstraction. See that I add now. I didn't add parentheses because they really have to do the first thing. So this is an incredibly simple find-replace operation. Literally replace every x with y. Let me give you some examples. So we have lambda x dot x and we're replacing x with foo. What's this going to return? Lambda foo dot foo. There we go. And so we can do it step by step. Just blindly following these rules. These are just simple computation rules. We can blindly say, okay, this hits that case of the meta variable being the variable that we're interested in. That way we rename the meta variable to foo. And then we rename the body. And then when we get to the body, the body is an ID that is the variable, the ID that we're interested in. So that's going to return foo. We can do something crazy like this. In this whole expression, we can replace x with bar. And it's just a simple remaining operation. We can take this. So we're going to essentially distribute this bar, this remaining operation to the outer application, the function application. So the equivalent of remaining this little expression of x with bar and in here, remaining x with bar. And in here, remaining x with bar. We're following those rules. Taking that one step, resolving this last one, doesn't change anything. Changing this step, changes that x to bar. And then we distribute this in here. It's going to change the meta variable x into bar. And then we will have to do the remaining operation on the body. We will then keep going until we distribute it all the way. Simple find replace. Replace literally, and this is mindless, right? This is kind of the interesting thing. This base operation, is this remaining operation, is super mindless. Just replace every foo with every x with foo for x with bar. Whatever. No, incredibly stupid find replace. Just replace every single thing of x with foo. Okay, now back to alkyl equivalents. So we're saying that if we have an expression e, so what we're going to do is we're going to find alkyl equivalents in terms of this simple remaining operation, which seems a little counterintuitive because this remaining operation is incredibly stupid. But what we're going to do, and we're going to find this actually incredibly generally, we're going to say that you give me any expression e, and for every variable that does not occur in that expression, any id you possibly want that doesn't occur in there, if you have lambda x dot expression, that is going to be alkyl equivalent to lambda y dot expression of x substituted with y. What does this mean? Kind of stated in a weird way, right? It's stated you're first giving any expression and you're saying that all variables that don't occur in e, so this could be foo, this could be y, this could be w, this could be z, any id that does not appear in there, if you put lambda x, if this expression is the body of some abstraction of lambda x, then this is alkyl equivalent to replacing x, this bound meta variable, with every possible id that does not occur there. So how do you generate from this alkyl equivalent functions? I give you a lambda expression. I said give me an alkyl equivalent function for this. How would you do that? Using kind of this definition of this logic. So choose a variable that doesn't exist in there and rename the meta variable to that variable that doesn't exist. Just do a stupid reaming operation and that new function is alkyl equivalent to your original function. So if I give you two lambda expressions and I ask proof and show me that they are alkyl equivalent, how would you do that? Yeah, so you can check inside the body. Check it by replacing all of the meta variable of one inside the body. If you can replace that with the same meta variable of the other, and it's the result of this reaming operation, then you know that it's going to be the same. Incidentally, the other variable in the second so it would have to replace, it would be possible to do a single operation and get a one-to-one correspondence. Yes, so let's look at this real quick. If we had lambda x dot lambda x dot x on the left, so e in this case is lambda x dot x. So we're saying if that's inside of a lambda x, that is going to be alkyl equivalent to lambda y dot lambda y dot y and lambda w dot lambda w dot w, literally any id that is not equal to x. And the reason is because inside this expression, if there's argument e bound, that's fine. It's not going to change anything by reading that. And this ensures the fact that it's inside here, it ensures that inside this e all these x's are going to be bound. There will be no three x's. So we're not going to be changing any three variables. That's the important thing here. And we know we're not going to change it to any three variables that are referenced inside here because we're choosing y's that do not occur in e. So if there's a global, if there's a free foo and bar in e, you can't choose those to rename x to. Examples. Does that answer your question? Let me do it, but let me know if it did. Okay, so is lambda y dot y alkyl equivalent to lambda x dot x? Yes, so we take this body of y, and we can say does x exist in this body? No. Then that means we can take lambda y dot y and rename the body, rename the whole thing to x, and it'll be the same thing. It'll be alkyl equivalent. But that sounds crazy like this. Right, y exists in this body. So by definition, the node is not alkyl equivalent. You can't rename that. You can't just use this substitution operation to do it. But now we need a little smarter operation as we're going to do. So this operation, incredibly stupid, right? Is that okay? If you replace the first equation and the second equation by a k, a lambda k here, and then replacing every x with k, that would be good. The key is because this y does not occur. This y occurs in the body of this abstract. That would be very important. Why don't we change the w? Yes, very good. So if we get rid of this second thing entirely, we take this body, we see there is no k in here. So this thing would be lambda equivalent to lambda k dot replace every x with k. That's another good point. So the free z here gets replaced with w, which should clearly not be the case. You can't change free variables to point to something else. That's definitely not alkyl equivalent. So it fails onto your content. We have to use a variable y. Yes, in the body. We can't use any other variable. You can't choose any of these variables that appear in here. So it says for all variables that don't occur in here. So because y appears in this body, you can't choose it as the meta variable to change everything to. But this substitution, this... Sorry, I'm kidding on myself. This remaining operation is incredibly stupid. It is replace every instance of x with food. So you can think on one hand it's very simple, which is nice. It's easy to understand. You could literally just replace every x with food. However, if we just tried to do this and we talked about, OK, a computation is going to be we're going to replace parameters with their arguments. We saw that that could get into some difficulty. So it would be nice to say have a smarter operator that knew how to actually substitute one thing for the other. And kind of what I'm alluding to where we're going to end up going, right? So we have this lambda x dot plus x1. And again, we have to find two. We have to find one. We have to find plus. We have to take on... Well, you have to take on faith that they will exist at some point in the future. But if we have this, could we just use... So we're saying we're going to replace every instance of x in this body with two. So the question is, can we just use remaining? Can we use that remaining operator we just said to say, hey, in this body, so is this, if we wanted to compute and actually call this function, could we say, well, this is the same thing as this if we replace x with two. Yeah, okay, so let's think about this. What about at the first problem? The remaining operator is only defined with ids. We can only replace one id with another, which makes sense because one of the rules is replace the meta-variable with whatever you're replacing it with, right? And we can't have an expression as a meta-variable. So the types don't match. Furthermore, we don't know if inside the plus or one function, what if they're using the variable named x somewhere, right? We don't want to replace those x's so to change it a little bit. So now I want to compute this. I want to do what we'll see is called a reduction. So we already said we can't do something like this. But what would this do? Let's say if it worked. So let's say we didn't have an expression here, we just had y or something. Stick with this. So what are some of the problems here? So a, we can't replace this x with an expression. First problem. Which x do we actually want to replace? Wait, sorry. I think you're right. I think my parentheses are off. Which x do we want to replace? Do we want to replace this x? No, we want to replace this x. We only want to replace the x's that are bound to the meta-variable that we're replacing, right? But the substitution operator is very stupid if it replaces everything. Which may not be what we want. The, did I say substitution? The renaming operator. Sorry, these things are very similar. Which means we need a renaming operator. No, we need a substitution operator. And I wish one of you would just like tell me what I just said was wrong. And so this will allow us to replace, so renaming only allows us to replace one variable name with another. So you can think of it as very much a syntactic transformation, right? Transfer one IDK to another IDK. But when we do function calls, we actually want to substitute. We want to say substitute for variable x some expression x, y, z. We want to actually replace x in the body of this land abstraction with the parameter that was, the actual parameter that was used with two. So we saw we can't really use renaming. And so we need another operator. So we want to be able to replace a variable with a lambda expression. And this really is a key part of lambda calculus. This is going to really come home. And once we do this, and once you have this calculation, it becomes really easy. Pretty easy. So we're going to use this syntax. So we're going to say we're having an expression. Now we're using these square brackets. And we're going to say e replacing x with n where e and n are both lambda expressions and x is the name. So like type systems, once again, we're replacing just an ID with an arbitrary lambda expression. So it seems like it should be really simple. We just saw it. We could rename variable names. And like in this case, it seems incredibly simple. We have a lambda expression. We want to replace x with 2. Bam. Replace every x with 2. Done. Let's go home. But what happens here? If I want to replace every x with 2, what should this return? You shouldn't substitute because this x refers to this parameter. And so we really want the same thing. We want to leave this unchanged. So really what we're trying to substitute is we're trying to substitute every 3x with this expression. So like something in here. So is y a free variable? Yeah. So we want to replace y with lambda z dot xz. So what do we do? We just substitute it and replace y with this. Right. So before, what was x bound to? Nothing. So free variable. But by trying to substitute it in for this free variable y, because there is a meta-variable x, we're now saying that this x is bound to this x. But that's not really what we want. So if you think back, it's actually kind of like, I'm not really going to get into this, but it's kind of like passed by name, where the scope is determined by the invocation. Variables are bound at invocation time, not when you run them dynamically. So it's kind of a similar thing here. Just because we're trying to substitute y with this expression, we don't want to change what this expression means. So what should we do? What can we do? We're doing computation. But in this case, what can we do? Why don't we get rid of this? So let's think about this. Can we change what we're substituting y with? Can we change this x to be something else? Would that be alpha equivalent? We don't have a way to change that because our simple replace algorithm can only change things that are associated with the meta variable. So we would have to change stuff in the expression to the left. Let's think about it a little bit more rather than just the operators that we have. But if we change this x, it's like trying to call a function and saying, hey, I want to replace this with something that refers to a global variable. But then now you're changing that to some other global variable. You're changing the intent and the semantics of this lambda z dot xz. What could we change here? What would we change z to foo in this? It doesn't help us, but it's still fine. That expression that we're substituting is alpha equivalent to the original one. If we change x, that's not alpha equivalent. But that doesn't help us because the z is not the problem. We have a bound z. It doesn't matter where we put this in the expression. This z is always going to refer to this z because it's the closest bound it possibly can. So then what should we do? Change the x in the first thing. To what? Anything that doesn't appear to be the one. Let's just change it. If we change that to w, before we do the substitution, now we substitute y for this. Now we've actually preserved the semantics of both operations. This is still alpha equivalent to the original one. We've just substituted this in here and this expression is alpha equivalent here. That makes sense, the pitfalls of substituting. How do we actually define it? What are our definitely not convoluted rules that follow logical sense based on the examples we just discovered? So we have our options, right? Just like we did before. So we have three cases. There's only three cases. It's not easy. It's so simple. Three cases. What's the first case based on what we did for renaming? Only id. So what's the case that we care about that's going to result in a change? If the id is what? The same is the one we're trying to replace. If it's x and we're trying to replace x with n, then it's n. What about the other case? In this one. So we have an id that's what? Not x. And then what do we return? Whatever it was, not x. I mean, we don't return not x, but you return z. Or y in this case. That's it. And these two rules are identical to renaming an operation, right? Because here we just have a free x. If we see a free x, we're going to replace it with n. And that's not going to do any problems. If it's not x, then we just leave it alone. Cool. Yes. Because an expression can be a single id, this definition can replace the simple renaming definition. Like that operation is fundamentally the same as... On id. Well, except not really, because n is now an arbitrary expression. So you can't just go from one to the other. You could just go from renaming on id's to substitution on id's. That would be the same. You can't go back. The other one's incredibly simple. We have function application. You rename substitute pole. Function application doesn't change anything. What is the thing that changed and what do we have to worry about? So what's the third case? We've got two cases here. Abstraction. So when can we just substitute directly into the body? n is already 7 x squared. When 1's not bound. When g goes there. Let's see where we're going. Okay, perfect. Okay, yes. I definitely knew we were going there. Okay. So we're trying to substitute x with n. But we're trying to substitute free x's with n. So if we have a abstraction and the meta variable is x, what should we do? Not replace it. Not do anything. Don't go into that body. You can think of it like the abstraction is protecting that x. And saying all x's in this expression are bound to me or one of my sub-abstractions. So you shouldn't replace them. We're only trying to replace free x's with n. We have a lambda y. So we have a meta variable that's not the same. Yes, this is the key. So the thing that matters, when you try to substitute that lambda expression in the previous example, the problem was there was a free variable in n and that free variable was one of the meta variables of one of the functions. So if we go back. Right? So we have, so a, we only care about the free variables in n, the thing we're trying to substitute. So here we have a z, sorry, we have an x, a free x. And we're trying to substitute in here in a lambda x dot yx. So in this case, we didn't go into it based on the third rule that we had. Right? We're saying x is the same thing. This abstraction basically protects it. We're not going to go in and change anything. But here, the meta variable is x is different from the variable we're trying to replace. And so, in this, well, so there's two cases. Just a second. So, this is the different. So, this has a free variable x. n has a free variable x. Does, is x this meta variable? No. So then I can just replace it directly, right? I can apply this remaining operation in the middle. So this would be something like, and that would apply to each one. And eventually, we will get to lambda w. This will be replaced with this. And I've got to make sure that at least this is one thing. Let's see if my friend sees that one. It's not yet, but they do not. So, last case is if the meta variable thing is not the same. So we get slightly convoluted, but remember, we've already been studying free variables, bound variables, all that kind of stuff right here. So we say, if x is not equal to y, so the variable we're trying to replace is different than the meta variable of the abstraction. And y is not a free variable in n. Then we just apply the substitution operation in the body. Previous example, how do I say this? So in this case, if w, the meta variable on the far left, was a z, and that one was a z, then can we do the substitution directly, would it matter? What does that rule say? The rule says, so first, are they different? z and y? Yes. Yes. Is z a free variable in n? No. Nope. So then we can do it. And the way to think about that is even though we're replacing this y with this, this doesn't change the binding of this z. It's not going to accidentally be bound to anything else. This z will always refer to this z no matter where you substitute this. Because there literally can't be any tighter bindings of this z. Okay, then we get to the actual complicated case. Okay. The intuition here is we've exhausted all the cases. We said they're exactly the same as this. If they're different, but y is not a free variable in n, do this. So then the third one is if they're different and y is a free variable in n, then we just choose a different variable name and rename all the y's in here to this new variable name. Y prime. As long as it doesn't exist, well, to be safe, you just choose one that doesn't exist in either n or e. What are you going to do again? So we call it a fresh variable name, right? Just make up a new name. Call it y prime and use the remaining operator to rename in the body every y to y prime. And then apply the substitute x with n. And the important thing here is that y dot e and lambda y dot e and lambda y prime dot e replacing y with y prime are also equivalent, right? So if we're saying y prime is a fresh variable, that means it definitely doesn't appear in e. And so we just said that we can replace inside e with any variable that doesn't appear in e. And they'll be output equivalent. Is that the term on Monday? Exactly. At what extent do we have to have this burned into our brain? With person follow, you can kind of remember how to do it without remembering all the rules, but inevitably you're going to miss something. Don't miss anything? Yeah, it's the intuition that you're trying to substitute. You don't want to change any binding. So if it's ever the case, so you need to understand about three variables in this thing you're trying to replace. And you can even go so far to just rename in your expression. Just rename all those variables, three variables that are the same, to something different, right? That would make sure that it's not going to be the case. You need to do that before you do the remaining operator. Cool, so examples. Go ahead and do some examples really quickly. Replacing x with foo doesn't do anything, right? Because the abstraction protects x. Here, replacing x with 2 does exactly what we want, right? We're going to distribute that and then we're going to replace that x with 2. Here, we're trying to place y with lambda z dot x of lambda x, but the x that is guarding this abstraction is not z, so we don't ignore it. But x is a free variable in this n, so we have to rename first, rename let's all y's sorry, all x's to w's do that remaining operation and then do the substitution operation and we will eventually get lambda z dot z dot x z w, yeah. For the first example, even though the expression happens to be an id the abstraction still guards against the substitution. Yes, in every case. So because we're trying to substitute so you've got to think this is so we can do that we can do that free name we can do that substitution Yes, exactly. Yes, we cannot do this with substitution Exactly. And the way to think about this is if this so where this comes from is a program like this so replace all of this with call this a poo, right? So this means replace every x in here with poo how many x are bound to this x where's this x bound to this x this right is the tightest most bound let's go tables exactly so when we replace this x we want to replace all inside here we want to replace all three x's with poo how many three x's are there zero so the substitution operator does nothing we'll give you a little more more examples, we can do all your stuff goes like this, you can watch slowly on the video and now we're right before execution we can just learn everything to actually compute things