 Alright, second to last class, everybody super excited about that? A little too enthusiastic, come down there this year. Just a little bit, I feel like, aw, I mean, yes, we're happy that we're finished with this class because we learned all the material really well and we have a deep knowledge and understanding which is why we're happy to leave, but also better to be on the little side. Good, questions on project 5 before we start then maybe I can address now, as opposed to later. Anything, any other questions? A statement that was next, if it's pointing to just a no-op, then it will, if a type isn't assigned to a statement, there is an automatically assume it's a no-op, there is no type or old return here. I guess that's my question. If you don't set a type to a no, then how would it know what type it is? It would just be whatever garbage is in there when it allocates that number. Yes. What do you use to set the type to zero? You should just set it. That's the better way to do it, right, so relying on this type of behavior. Yes, explicitly set it. Good programming practice. Anything else? Nothing? Everything good? Alright. Okay, any other things on class? I'll assign the homework later to today. Homework 5, which will be based on my advocacy for doing today. Yeah, I think it should be good, because then we have what, the midterm in a week and a day or something like that. What's it? On the 7th, right? Yeah? Whenever it is, I don't know. What's on the thing? The final. The next test that's going on, sorry. Are you going to be posting a review guide pretty soon? Review guide? Are you doing a review guide for the final? No. So it'll be, because you have two midterms, plus this next homework with the covered lambda calculus, so those will be the review guides that you'll need. Okay, cool. Yeah, so everything on the class. As far as midterms, we're going to do everything we can to get them graded and entered in Wednesday before class so we can hand them out Wednesday. So I think, depending on how far we get through here, we'll have one more topic to cover on Wednesday, and then we can do a midterm two kind of in class review so you can go over that and then answer any questions for class in the final. Yes, that's the plan. Think about that. There's like 150 papers in there. It's kind of a pain to... I can't legally, because you can't see anybody else's grades. There's grades and names on the front. Yeah, it was a mistake, but... Any other questions? All right, I'll think about it. If you remind me that I have greater chance to remember to do that. Anything else? All right, so on we go. All right, so now lambda calculus. We've been talking up. We've been building up everything we need to know to understand lambda calculus, right? We've talked about the syntax. What do lambda expressions look like? We talked about the four different forms that a lambda expression could be in, and then we started talking about semantics and kind of building up these operators that we really want to have in order to finally do some kind of computation on lambda calculus. So we left on Wednesday with substitution. So what's the difference between substitution and renaming? Let's go back a little bit. What does renaming do? The renaming operator. I know there's a lot of turkey in between now and then, but maybe, yeah. Do they look very close to each other? So basically, you can pick up a textual replacement, right? It replaces all occurrences of one variable with another. So what's the difference between that and substitution? Placing a variable with an expression, right, exactly. So in renaming, right, we're just renaming a variable as another variable. With substitution, we're substituting a variable with an arbitrary expression. And so this seems really simple, right? So we should be able to do this fairly easily. So this is the syntax that we're using, the brackets. And we're using the variables on the left, and it goes to some arbitrary lambda expression on the right. So here, it's pretty easy, right? We want to replace x with 2. What do we do? What's the result of this substitution operation? Three. Just do one step. X is now 2, and so the resulting expression is going to be what? Plus 2, 1. Yeah, exactly, right? So we just want to place x with 2. But then what happens in a situation like this, right? Here we have x is the variable we want to substitute and we want to substitute it with 2. But what's the difference here between this case and the previous case, yeah? We can't rename the first x because it's going to change to that name or something. Almost, not quite. What's the difference between the x in this expression and the x in this expression? It's bound. It's bound, exactly. So we can't just replace all x's here with this expression 2 because this is an id, right? It doesn't make sense to replace that here. So one thing about substitution is it leaves, we're leaving abstraction bound variables the same. So we only replace free variables. Make sense, right? Okay, now what if we want to do this? What if we want to replace this y with the expression lambda z xz? Can we do this? So in this expression is y bound? No. No, so it's free. So we can actually, we can do it, right? But what happens if we just simply mindlessly replace this expression in here for y? What was that? x would be bound. Yeah, x would be bound to this abstraction whereas this expression that we're replacing it with, right? We actually want it to be, it's a free x. It's not bound to any abstraction. So if we simply place it here, well this x is now bound to this abstraction over x and now we've done this replacement but we've done it the wrong way because now this isn't semantically the same. So it's one thing we can do. Rename what? The bound x to what? Yeah, something, anything else that's not used, right? So we can just create some new variable. We can call it w where we'll replace all of the bound x's with w and then we can easily do the replacement. So this is why we just can't do this simple textual replacement. Okay, so this is kind of the intuition behind this substitution operator and the tricky cases. So now let's look at how it's actually implemented or how we're defining this. So here we have, we're defining this substitution operation for some lambda expression e. So what are the four cases that we need to think about and take care of when we're defining this? Expression, expression. Expression, space expression, so an application, yeah. What was that? Abstraction. Abstraction, so yeah, lambda id dot expression. What about the third one? Id, right? Yeah, expression, just id. And the fourth one is what? Parentheses. Parentheses, yeah, we can only care about. Okay, so in our first case we're going to say, hey, if we have an expression of the form id, right, and that id happens to be the exact id that we're looking for x, then we're going to replace that x with this new expression n. Right, makes sense? It's the base case, right? We have to establish our base case. Okay, what if this expression here is not the identifier x? What if it's the identifier y? What would the result of applying this substitution operation on y be y? Well, because it's not x. Exactly, perfect, yeah. Substitution operator, we want the semantics to mean, hey, replace x with n. And so if we get to an id that is not x, then we don't really care about replacing it, right? We're going to say, okay, well, you're y, so you're not x, so therefore we can leave you alone. You're going to be y, great. Okay, what about for application? Should we do something special for application? It's a variety of head movements. That's just good, maybe we should do stretching before class. So if we have an expression in the form expression one, expression two, and we're applying the substitution operation applied to both. So there's nothing special we have to do here, right? There's nothing, we just say, okay, well, we're applying the substitution to an application applied to both the left and the right. Okay, so the remaining case would be an abstraction, right? So, okay. We have a very simple case. Yeah, okay, so this case takes care of the case we looked at where x is bound, right? So if x is bound, we don't want to continue this substitution operation into this expression, because what do we know about every occurrence of x in this expression e? Not exactly, right? So we don't want to replace it because it's bound to this abstraction. So we can't just replace every x in here with n, and so we just leave it alone and stop here. So what if this variable is not x, then what should we do? Replace. Replace, pass it in. So let's say we have this form, right? y dot expression. So yeah, what we want to do is we want to pass this substitution operation into this expression, but we need to be careful of one thing, right? Which we saw earlier. If y is a free variable in n, then we're going to have a problem, right? Because we're going to change the semantics here by changing this y off of this. If there's a y in n and we replace all the appearances of x with an n, well now, any of those y's in n are going to be referencing the bound variable and not the free y. So what do we do? So what's the other case here? y is a free variable in n. Name the bound y. Yeah, exactly. And do we have an operation to do renaming? Yes. Yes, that's why we did that. Okay, yes. So the last final remaining case is, okay, we're going at abstraction. The abstraction, the meta variable is not the variable we're trying to replace, right? But if y is a free variable of n, then we're going to generate some new variable name. We'll call it y prime, right? Just any variable name that does not exist anywhere in this expression, right? And then we're going to say, okay, replace e first. This is going to result in an abstraction over y's prime, right? This new variable y. And we're going to first replace all instances of y with y prime in the expression, just doing the renaming operation. And then we can properly replace all instances of x with n. Does that make sense? It's kind of a formal definition of our intuition of what this substitution operator should mean. Questions here? So everybody can go do this. All right, then we're going to have some examples. Okay, so we want to substitute in this expression, we want to substitute x with foo. So what do we do? Nothing, y nothing, because we're lazy. X is bound and what about x? It's an abstraction, but it's also the variable that we're trying to replace, right? Yeah, exactly. So that's, it's the variable in the abstraction, so we stop and we just return this, right? It's explicitly one of the rules. It's very easy to do. Okay, now we have another one, right? So now we have, okay, plus one x. So now we have, we're going to replace x with two. So what do we do? What's the result of applying this operator once? Plus, plus, plus? Yeah. Plus one two. Plus one two, not quite. So we're doing this very precisely, right? So we want to apply those rules. So which rule applies? What was that? I guess I'll be melting it. Yeah, no, you. Bye. Check it, okay. Application? Application, yeah. It's an application. So then what do we do with this renaming operation, or this substitution operation? Apply it to both, right? Just apply it through. Exactly. I did a little bit of a short pick here, right? I applied it to each of these three elements, even though they're not quite, it's not exactly how we grouped it. So, right, each of these is an expression. So I'm just applying the substitution operation on each of these expressions. So on this expression, plus x goes to two. X is replaced, substituted by two. What does this result here for this expression? Plus. Plus, what's the result for this expression? One. One, what's the result for this expression? Two. Two, yay. Let me get plus one, two. That's how we get there. Okay, now we go back to our example, right? So, a different example. So now we're trying to substitute y with the lambda expression, lambda c dot xz. So here we have an abstraction. So do we stop, like we did in the first case? I'm seeing kids shaking y. Y is not a bound variable. Yes. But also, x is not the same as this y here, right? So we can apply, so the first thing we do is we apply this to the body. But we have to do, there's another case we have to check, right? What about x? Say it again? Rename it. We need to rename it y. Because we have another x. Right, because it's free here in this replacement, right? So we need to actually first rename this body. So let's use the variable w, since that's not used here. So we're going to replace this, and we're going to apply our rule, and we're going to say, okay, rename that x to w. And then in the body, the body is still yx. But first we're going to rename every instance of x to w. And then on the result of that, then we want to substitute any y for lambda z dot xz. And so what's the result of this renaming operation? What was it? Yw. Yw? Yeah. So we apply that renaming operation, rename that yw, and then we apply this substitution operation. So we apply it to both of them, exactly. So we're going to apply it to the y and the w. Yeah, question. Is the bound x the one major place instead of the one you're substituting? So why the bound x instead of the free x is being replaced? Exactly. So we know from looking at alpha equivalence, that if we change the names of the bound variables, we haven't changed that function at all. It's still the same function. So we can easily change the name of the bound variables, but if we change the name of any free variables, now we've changed the semantic meaning of that replacement. Okay, so I'm going to replace what happens to this expression here, w. W. And this expression here? Lambda z dot xz. Yeah, so we get lambda w dot lambda z dot xz, apply to w. Pretty easy? Makes sense? All right, let's do, I think, let's look at some more examples. Okay. So here we have x, the expression we have is x applied to lambda y dot xy, and we're going to replace x with yz. So what's the first step? What do we do? Exactly. So distribute this substitution operation to both this expression and this expression. We have x is substituted with xz, and this x is substituted at xz. Okay, so what happens, let's say we do this substitution first. What's the result here? xz, exactly. And the other one stays the same. So then we apply this to this expression. So what's the result here? Which of those cases? We have to rename what? The bound y. The bound y, y. And somebody said it. It's free. It's free, yeah, exactly, right? It's not just that it appears here. So if it appeared here in an abstraction, then it wouldn't matter. But it's because that it's free and it appears here that we have to rename it. So let's rename it to q because that's a fun letter that we haven't used yet. So we rename this to q, and then we're going to have this expression where we rename every instance of y with q and then substitute x for yz. So we're going to perform this renaming. So what's the result of this expression renamed? xq. And then we're going to apply the substitution on it, which is going to distribute it, and we're going to skip that step. And so the result here is going to be yz, where x was. Okay, any questions on substitution? So this is going to form the basis of our computation. So this is very important. It's also very important that you know how to do this. Yeah. So in this case, if x were a bound variable, then if we would do a renaming, it would only be just directly with the substitution. Correct. So if this was a lambda x here, you would replace this x because it's free. So when you distribute it, you distribute it the same. This would evaluate to yz, but this x would never go into this abstraction because x is bound. Cool. All right, this is where it really gets interesting. Okay, so we're going to define execution as a sequence of terms. So you can think of this as resulting from calling or invoking functions. So basically we're taking that application and we're actually applying it to get some result. And this is where we introduce a bunch of terms that are important to lambda calculus. So what we're going to say is that every step of this function application, we call a beta reduction. I guess it's differentiated from alpha equivalence. So it has a different name. And so we're going to perform a series of beta reductions. And so every time we can perform a beta reduction is when there's an expression in an application form, which we'll see, that's called a beta redux. So it's in this form. So we have an expression of the form e1 space e2 and e1 is an abstraction with somebody and e2 is just whatever. It doesn't matter. So basically this means, hey, here's a function definition. It's being called with one parameter, the parameter's n. So as long as there's one of these, it's called a beta redux. And we're going to do this and reduce it and we're going to see how we do that. It's going to be very simple based on what we've seen so far. So basically what is, so we're saying this is like function application, but what does that mean? So what is like semantically, or maybe using some of the operators that we've defined, what does that mean here? Say that again? Replace all x with n. Replace all x with n. Yeah, exactly. Or substitute, right? So yeah, this is why we've been building up here because this actually forms the basis of our computation. So what we're defining is that this lambda x dot e applied to n beta reduces to e where every x is substituted with n. So that's it, right? We just call a function and it returns some new expression. And if this expression with x replaced with n has more beta reduxes in of the form lambda is applied to some n, then we can keep applying this. And we know that we're done when we have an expression that is in a beta normal form, which means that there's no more reduces. We've applied all the functions we possibly can, and so we've stopped computation. So full beta reduction is when we've reduced everything regardless of where they appear. We just keep applying these beta reductions and we may eventually reach a terminating state and if we do, then we've reached the full beta reduction. All right, this is a little abstract, so let's look at some examples. Okay, so we had the expression lambda x dot x applied to y. So is this in, can we apply beta reduction to this? Yes. Yes. Why? Because there's a function here on the left and we're applying it to something. So what's our step? What's our computation step? If we wanted to beta reduce this, what would it be? Not the final outcome, but at least the first step. X where x is substituted with y. Yeah, so we're going to find this, the body here, x, which happens to be x, where x is substituted with y. And then can we do the substitution based on all those examples we just did? So what's the result here? Y. And is there any more, can we do any more beta reductions on here? No. No, right, because it's just an identifier. There's nothing in the form of lambda applied to something. Look at something slightly more complicated. We have lambda x dot x applied to a lambda x, so the body is x applied to lambda x dot x, and we're applying this to u applied to r. So can we apply u to r first? Can we reduce this? Why not? It's what? This is two expressions, right? So why can't you do this one? Yeah. It's an application. This is an application, though. Yeah, yeah, wait, let's go there. Sorry, I love you. So how do you know? Yeah, so the way to think about it is the left-hand side is not an abstraction. It's not a lambda. It's just an ID, space ID, right? We're here. This is, the left has to be a lambda, a has to be a lambda expression, or a, they're all lambda expressions. This has to be an abstraction. So we're applying abstraction to some expression. Exactly. So this is why we know we can't do this. Could we apply this body here? So there's parentheses here of x and lambda x dot x. Could we apply that? What was it? This is just defining an abstraction. So this is, so for it to be in beta reducts form, right, we have to, there has to be an application. So like here there's an application, where the left side is an abstraction. So inside this body, that's not really the case, it's just defining an abstraction, right? Yeah, I don't know what I'm saying. Yes, but is the left-hand side here an a lambda expression? An abstraction? But this is the body, right? So you got the body inside this abstraction. Is x applied to lambda x dot x? Yeah. Yeah, well, it's good. But we may get there, right? So, okay, so there's only one beta reduction we can do. So what abstraction are we applying to what expression? So the outermost application, right? So we're going to be substituting in this body x with the expression u r, right? So you can think of it like, okay, we get rid of this outer abstraction, then we say, okay, in here we substitute x for u r. Okay, so what's the result of this? So we've gone through this in detail, right? So shortcut a little bit. So what's the result here? u r in parentheses lambda x dot x. So how come x didn't change in here? Yeah, exactly. So if you see how this like takes care of all that scoping rules that we define, right, the way we apply this, is this x is bound to this x. This x is bound to this x. When we're applying this function, right, and we call this function, we replace every occurrence of this x that is bound to this abstraction with the parameter. But the awesome thing here is, once you take off this abstraction, right, and you just have the body, any free x in there was bound to that abstraction. We don't need to worry about any of the other x's because they're bound to other abstractions. So can we reduce this further? No, right? We just have identifiers here. We don't have no more functions. So nothing's in beta-redux form. So this is in beta-normal form. So we've done as many steps as we can. Okay. Look at something a little more complicated. So how many reduxes do we have here? Any? Can we do any reductions here? Somebody give me one? X.y applied to the tire right-hand side. Yeah, we can do lambda x.y applied to the tire right-hand side. What else? Is there anything else? Yeah, exactly. So we have two here, right? So we have this inner application and the outer application. And it's not something we're really going to get into in this class, but it turns out it actually doesn't matter which one you reduce first. As long as you keep applying any beta reduction to any reduxes that are left, you'll eventually get to the same thing. So it doesn't actually matter which one you do first. But we'll do the inner one first. So we're going to replace z with lambda w dot w. So does that require any renaming? No. No? Why not? What is it? Z and w are different. Yeah, z and w are different. And you can also say there's no free variables here. Right? So we take the body and we say, replace every z with lambda w dot w. Right? Who can easily do this? We do this, right? Replace every z with lambda w dot w. Now how many reduxes do we have? Two. Two. So we still have the first one. What's the second one? Yeah, lambda w dot w applied to the lambda w dot w. Right? So did this reduction step actually reduce anything? Yes. I guess I'm using reduce in the more informal sense. It did make the body smaller. Smaller how, I guess. Ah, now there's just one w. Yes, twice. Yes. Okay, yes. It did change something, right? But I guess we have to keep going to figure out what it actually changed. Okay, so let's do this middle part, right? So let's say, okay, so now we're going to apply this to this. Do we have to worry about any variable renaming here? Yes? What do we need to rename? I can't hear you. Either of the two. Either of the two? Yeah. Right, so we're going to replace w with this thing on the right. Does that mean we need to rename w? It appears here. No? I know. Exactly, so the key is on the right here, w is bound. So we know that it doesn't refer to any global w. It only refers to this w in here. So therefore we don't need to do any renaming. So yeah, so we're going to, the next production step, we're going to take the body, which is w, and we're going to substitute w with the abstraction w dot w. And then we can do this pretty easily, right? This case is trivial, so we replace this w with lambda w dot w. And now how many reductions do we have? One. Yes. Okay, now we can do this production, right? So we're going to replace every x with lambda w dot w in y. What does that return? What was that? Y. Y, exactly. We did all this computation and got it. So why did it give us y? Because there's no unknowns. In where? In the very first one. There's no bound x. Right, so it just discards its parameter and just returns y. So it really doesn't matter what we compute here. So this is why maybe you can see, if we applied this thing first, well then we are replacing x with this whole thing on the right in y. But there's no x in there, so we just always return y. Questions on this example? That's kind of tricky. Yeah? Yeah. I was trying to go through these where you discovered that both lambda w dot w is on that step. Oh, here? Yeah. Okay, so all we're doing here is applying beta reduction here. So how do we apply this to this? Right, so we take the body of the left-hand side and we say whatever the meta variable is, replace that meta variable with the right-hand side. And so this is, okay, so the body is w. So that's an expression. So inside that expression, substitute w for lambda w dot w. And then we do that and it returns lambda w dot w. More questions? This is important because the more we keep doing this, the less of the detailed steps we're going to do because it gets kind of crazy when you have big functions. All right, we'll do another example. Lambda x dot xx, apply to lambda x dot xx. Which means fine, right? So can we do this application? So there's only one redux we can do, right? So can we do this production? Yeah, so what are we going to do? Why are we renaming? Are these x's free? Are they? Say it again? So what's the body of this lambda extraction, of this abstraction? xx. It's everything from the period, right? It's sent as far as possible. So everything from the period to the right parentheses. Right, so both of these x's are in the body of this abstraction which means they're not free exactly. Yeah, so we don't even need to worry about any renaming. Exactly. Okay, so then what happens? Then what do we do if we don't have to rename? Right, so we take the body of this less abstraction and say, okay, substitute the parameter x for the thing on the right. So we have the body xx, substitute x, replace that with that. So what's this going to be? Same thing we started with, so can we reduce this? No. We can't? We can reduce it, right? It's just a one-step process. We're going to apply it, are we going to reduce it? Can we do it again? We can do it again, right? It's going to be the same thing. Is that really reduction? Do you end up with exactly what you started with? So, yes, it's still a reduction, right? So just like you can write a program that never terminates, right? You can write a lambda expression that doesn't have a full beta reduction, that you cannot fully reduce, right? Because in this case, it just keeps growing, or it actually just stays the same. You can write expressions that keep growing and keep growing bigger, and so then you're kind of out of luck there. So this is kind of an example to show you that, hey, yes, each of these steps is a beta reduction, right? You can just keep applying these steps, just like your program executes, right? You may not know or you can't prove that it's ever actually going to stop terminating. Okay. So I'm trying to make the claim that up to this point, so this is how we perform execution and perform calculations. I'm trying to make the claim up to this point that, okay, this really is a programming language. Is this in any way useful in any terms of programming language from what you've seen so far? What kind of things are we missing? Let's flip that around. What do we have? Substitution and renaming. Substitution and renaming? Although those actually aren't part of our program, right? They're not lambda expressions. So just thinking from lambda expressions that you can write, what do we have? What was that? Yeah, function calls. That's pretty much all we have. Do we have anything else? Like, we can define functions and we can call functions. Those are pretty much our only two operations that we have. And yet, we made a program that executed forever, right, that didn't terminate, so I guess it's kind of easy to write a buggy program just like in this. So what are some things that we would want to perform some actual computation? What was that? Arithmetic? What else? Conditionals. What was that? Conditionals. Conditionals and outputs. It's like an implementation detail. Is that all you need to program? I'll give you function calls, arithmetic, conditionals. Floating points. Floating points, that's another. Loops. Loops, yeah, right? You want to write some kind of loops to be able to some kind of iterator data structure. Yeah, exactly. Okay, let's look at one of these firsts. So the first thing we're going to tackle is Boolean logic and conditionals. Okay, so believe it or not, this is going to seem crazy, but you can write all the Boolean logic operators in using only lambda calculates. And it is a little bit crazy. It kind of makes some sense sometimes. The other thing is there's an infinite of ways that you could represent this type of thing. So we're going to use the definitions that have been kind of already shown to be correct. And this comes with defining, okay, we're going to define what, first we have to define what true and what false is in this language. So we're going to say true is a function, it's an abstraction that takes an x, which has a body that's an extraction that takes in y and then returns x. So semantically what is this function? You have to give like a type to this function if you will. So we talked about currying, right? Which means that we can write a function that could take in many parameters to a function, just a series of functions that takes in one parameter. But if we kind of uncurried this function or wrote this in a real language, how many parameters did this function have? Two. Two? How do you know two? Just x and y. Right, so we have two abstractions here. So that first abstraction returns another function that takes in an abstraction, that is an abstraction. So really if we were looking at this like a function, it would be a function that takes in two parameters. So what is a function return from its two parameters? The first parameter. The first parameter. That's all it is. So it's not any different really than we saw the function that takes in x that returns x, its own parameter. Okay. If you kind of have to go over this, we'll see these things in action. So we're going to find false as the same thing, but what's different here? It returns a second parameter. It returns a second parameter. So that's it. Those are our definitions of true and false, and using these definitions we can build up arbitrary Boolean logic, which is nuts. Okay. And so what am I doing here by saying t is equal to this? Is t now an identifier? It's an expression, right? Exactly. So it's just a simple placeholder for us, right? This doesn't really mean anything to land up at this. Every place we see t, we can replace it here with this. So are these combinators? Yes. Yes, how do you know? Because it's a 50-50 chance, yeah. Say it again? Yes, there's no free variables. That's how I know it's a combinator, right? So I can just replace t with this thing at any point in time, right? Okay. Now the next thing is we're going to look at the function and. So before we look at what is defined, how would we write the function and? Like, think about types. What would we want it at a high level to do? How many parameters should it take in? Two. Two? And how many should it return? One. One. Exactly. And okay, we'll look at the semantics of what we want, but at a high level, right? We want, so what we're going to, the bottom of this is actually going to make sense, but it's going to take in two parameters, a and b. And the body is abf. So how would we parenthesize this? Where do you add parentheses? Before a. From the ab? From a to f. And from a to f? Is this the body here? Okay, good. All right. So what do we want this to return? And tt. What do we want? Another t. Yeah, how do we know what it returns or computes as equivalent to t? So yeah, we want alpha equivalents, right? We want to make sure whatever this and tt should beta reduce to something that is alpha equivalent to t. So that's what we kind of, when we're talking about equality here, right? We mean alpha equivalent. Okay. So we can easily do this, right? So we know that the body of this function is ab, ab, sorry, ab, ab, and then false we have here. Right? So can we just keep beta reducing this just like we've seen in all of our examples? Yeah, we can do this. We've had to put true and true here. So let's do this for a big one. Okay. Here we have our and. Right? So we have ab, ab, false, true, true. Okay. So remember the way we've emphasized at least the topmost one, these two are applied first, right? We have left association on our application. So that we have this big land expression from the and the land abstraction is going to get applied with this. So can we do that? So what are we going to do? What are we going to substitute? We're going to substitute a in this body with what? T. Yeah, exactly. This T here. Right? So we're going to do that. So we take out the outer one and we say, okay, the inner one which is lambda b dot ab, replace a with essentially T. So we can do this, right? We're going to replace this a with this expression here. Now maybe we do it again. Sure. All right. So then replace this b with the T. So we then take out that outer abstraction. We're doing another beta reduction step where we replace b with this. Do we need to do any variable renaming? No. No, because there's no free variables in the expression that we're using, right? So we know we don't have to worry about that. So replace b. So what is this expression that we have right now? True, true, false. Are these functions? Yeah. Right? We can apply them. So let's apply them. So let's take the, we're going to take the left most again abstraction. So we're going to say in here replace x with this thing, lambda x dot lambda y dot x inside the body. So can we do this? Yeah. Right? So we're going to place that there. Do we have to worry that this y in here is this thing here? No, because it's bound, right? We don't care at all. Okay. Now can we do this replacement? Or this application, this beta reduction? Yeah, so we're going to inside this body here, we're going to place y with lambda x dot lambda y dot y. And what is this? Yeah, no replacement, right? There's no free y in here. So we get back this, which is? True. Can we beta reduce this? True anymore? No, because it's just a function definition, right? It's crazy, right? Let's look at another example. Okay. And true false. What should this beta reduce? Full beta reduction to false. Okay. So now we're going to use the, instead of going through the very detailed step, we're going to use the little bit more abstract thing here. Okay. So we want to apply this function once, right? We want to do the first beta reduction step. So what are we going to do? Replace a with true? Yep. So we're going to place a with true in the body there. And then we're going to trace, do what? What's the next substitution? False. Be with false? Yep. So now we have true false false. Right? So what do we say that true has a function? What does it do? Returns to the first parameter. Returns to the first parameter, which is? False. False. False. And in our previous example, what was the first parameter? True. True. Right? So that's why it returned true. We can actually compute this out. We can actually look at this and say, okay, true is x, a lambda expression with x, y, and returns x. So we know applying this f in here, we're going to substitute, whoops, any x with f. And now we have a function lambda y dot f. We apply that again, and reduce this, and finally we get f, which is what we want. Now, after we look at it again, right, this is the last identity that we need, or second to last, right, and false and true, what should this return? False. Yes. All that Boolean logic drilled into your head. Okay. What's this ultimately, what's this going to reduce to when we apply this lambda a lambda b dot ab f? False. False true false. Yeah, exactly, right? So this is pretty easy to see. We replace this a, replace this b. False true false. So now false as a function, what does it so many things do? It turns a second parameter. It turns a second parameter, which is false, which is what is built into the n function itself. And finally, just to be complete, right, we have to do the last one, what should this return? False. False. Okay. So we have lambda a, lambda b, ab f. So substitute the f's in here, what are we going to get? False false false. False false false. Everyone's great in this class. You guys, there's no f's at ASU, so don't worry about it. Okay. And f returns the second parameter, which is f, therefore we have f, right? So is this a valid and operator? Yeah. Yeah, pretty crazy, right? Yes. Yes. Very nice. And it kind of makes sense, looking at the definition here, right? So assuming that a can be either a true or a false function, true or false. If it's false, it's going to return the second parameter, which is false. So this takes care of if the first parameter is ever false, this function is always going to return false. If the first parameter is true, then it depends on whatever the second parameter is. If the first parameter is true, this function is going to return the second parameter. And the second parameter, if it's true, it's going to be true. If it's false, it's going to be false, right? So that's how this and function is written. Okay. Using that kind of similar logic, how would we define a not function? So what do we want from the not function? High-level semantics. If you give in a false and return a true, if you give in a true, return a false. Right. So we have one parameter, and if we say, if we call it with not true, that should beta reduce to false, and not false should beta reduce to true. So how can we write this expression, the body of this function? Take a check. Yeah. So what would that do? So passing a true to the first argument is an F. Which is false. Yeah. Passing an F to the second parameter. Exactly. So lambda a.aft, right? So we say, okay, well if a is true, it's true is going to return the first parameter, which is false, and if a is false, it's going to return the second parameter, which is true. Therefore, we have a not operator, right? So we can actually look at this pretty easily. You can say, okay, this is the not operator. This is a beta redux. We can reduce this. We're going to replace a with t. We have t true, false, true. And we know that that's true. It's going to return its first argument, which is false. And we can look at the nut other way, right? With not F. We can say a.aft. And we're going to replace a with an F. And we can say, okay, return the second argument. What's the second argument here? True. True. All right. So we're going to use this to write like or and and. Yeah. All right. We could do that. We just have to show that it actually works the way we wanted it to. So how can we do, can we do, so now we have our Boolean logic down. What about branches? How would we want the semantics, let's say, let's say so if we were writing in maybe c or Python another language, we want something like if c, then a else b. So now what's kind of the signature of our function? How many parameters does it need? It takes one parameter. One parameter? It takes it. It takes one parameter. One? Yeah. What's that parameter? C. C? Three parameters? Yeah. So we'd want something like this, right? And if function were c, the first one is the conditional, right? So if true, then return a, right? Otherwise return b, right? So this is kind of what we want the semantics to be, right? We want to say if c evaluates to true, then return a. Otherwise if c evaluates to false, return b, what does this already kind of look like? False and true, so how? So yeah, this is true. We already defined true as saying, hey, return the first argument and if it's false, return the second argument. So we can actually easily define this if function as just the identity. So to say return as a one parameter function, return the first parameter, whatever it is. And then if that function's true, then that will be operated on the arguments and then we'll return the true branch. Otherwise false will return the false branch. So here's an example. So we have if true ab, okay. So our if is just a simple lambda a dot a, right? So can we reduce this? Yes. Yeah. We have the lambda abstraction here applied to this term. So we're going to replace the a with the t. Now we have true ab, what is this going to return? A, which is exactly what we wanted to do, right? If the condition was true, we want a to be returned. All right, we can look at the same thing if it's fabulous. Then we can change if to lambda a dot a and we just replace the a with f and then we can say, okay, false returns the second argument, which is b. This is exactly what we wanted. So do we have everything we want for branching and conditions and all that? Hmm? Loops? Don't have loops yet. We're going to get to that. Okay, now let's talk about numbers. So this is also kind of crazy. But we're going to use the same ideas. So we want some way to represent numbers, right? But we don't. We don't actually have a way to encode a numeric operator. So we're going to find a function that represents essentially a number. So, and this is Alonzo Church. He's going to come up with this. That's why these are called Churches numerals. So we're actually going to find, so every number or numeral in our lambda calculus is going to take, be a function that takes two arguments. So we can see this was zero. So zero is very simple. Zero, what does this look like? False. Yeah, is it alpha equivalent to false? Yeah. So it's a function that takes in two arguments and returns the second one, right? It's kind of cool. Okay. What we're going to do is, for one, we're going to say that one is lambda f dot lambda x dot fx. So this would be the first parameter applied to the second parameter, right? So this happens once. So this is how we know it's the numeral one. Two is going to be the same thing and another application of f. Three is going to be... Three of these. Three of these, the same thing. And four. Four, yeah? I think I did that wrong. That's fine. Okay. So what's wrong with this parentheses that I did here? I did it the wrong way. Yeah, I did it the wrong associativity, right? It should be to the left. Yeah, so we're applying f to... f to f to f to f. So we're applying f to f and then f to ff to fff and then back to fff and then all that to x. So if we were to say... So this is just a function, right? So if we were to write maybe 4ab, what would this beta reduce to? Yeah, 4as and a b, right? So then we can know this function is 4 because there are four applications of this f, right? So five would be five applications. Six would be six. You could write any number you wanted just like this. Everybody with me so far? What's maybe kind of a weird thing, but what is... And so here we're only talking about, obviously, integers, right? So what's one of the properties of integers, let's say? So here we've defined them, right? But what kind of functions might we want on them? Addition, yeah, that's one thing. What about even before addition? Increment, yeah, we need to go... That's kind of one of the properties of integers, right? You give me any integer, I can give you the next integer. So you give me anything, I can be able to add one to it, right? So another way to think about this is the successor function, right? So what's... Give me a number, what's the successor to this number? And we can actually define this. So we're going to define this as a function. So what's the type of this function going to be? So how many parameters is it going to take in? One, yeah, it should return one numeral parameter, or a numeral parameter like true and false is a function that takes in two parameters itself, and it should return something that also has that same value. Yeah, exactly. Okay, so this is how we can kind of read this. So the successor function we're defining as function that takes in technically three arguments, n, f, x, where it takes f, applies that to n, applied to f, x. So n is the number that we want to find the successor to, and f and x are arbitrary stuff. So this is kind of one of these things that looks really weird so you start playing with it and you go, oh my gosh, this actually works, this is insane. Okay, so here we have zero, which is defined as f of x returns x. So let's calculate the successor of zero. So what should the result be? Which, how is that different from this zero here? Yeah, the body is f of x, exactly. Okay, so we have the successor function, and f, x, f of n of f of x applied to zero. So we can replace that n with zero, right? Okay, we're going to replace n with zero, and then we're going to replace this zero with this definition here. And what do we know about this definition of zero here? Semantic-wise. Second argument. Yeah, it returns a second argument. So it's just going to return x. So this is going to return x, and on the left we have this f. And so what's this? Yeah, one. So the successor of zero is one. Which is exactly what we wanted. Let's look at another example. Okay, here's the definition. Here we have one. We want to call the successor of one. So we're going to do the same thing, right? We take the definition, we take the function one. We're going to substitute one n for one in here. And so we're saying, okay, one applied to f of x. So what is... So we do this, we expand it out. So what is this going to be going to result, reduce to? Huh? F. F? Does it x? F will get replaced by the x. The x. So we do one application first, right? So we replace the f with this f, right? So we replace this f inside with this f. And then that returns lambda x dot f of x. And then this x replaces this x. But we just get lambda f dot lambda x dot x f of x. So what do we define this as? Yeah, two. So you can maybe see the successor function, right? If we look at the definition. It says, okay, whatever parameter you take in, n, is some numeral, right? The numerals just apply their first argument their number of times to the next argument. So whatever number you pass in, you're going to get in here, f applied f that many times to x. And then this just applies f one more time outside of that. And now you've got the next number. In here, yeah, you could, yes. But it works out fine. You don't have to, because you change this bound f, right? Just f prime, which changes this f prime. So you're replacing this f prime now with f. So let's get all those steps to do those replacements. And the same thing with the x, too. The important thing is that this f and this x are bound to this abstraction here, right? So that the results that you get, all these f's and all these x's are bound to these abstractions. Exactly. So it's saying, hey, take whatever number you give me, that's going to return you, that number fx is going to apply that number that many times to f. And then if we apply f one more time, we get the next number. And that's how you can always get the successor. If you give me the successor with n, I can give you n plus 1. Okay, so this is the basic step that you want to do with addition, to do with numbers. Now let's try to do some addition, right? So we want to add two numbers together. So now let's kind of the signature we're going to want here. What was that? Yeah, so two numerals in, one numeral out. Okay, so for some examples, since I always want to review. So we add zero and one, we want to get one back. If we add one and two, we want to get three back. So the addition is very similar to the successor, right? So we have n, which is going to be the first number. We have n, which is going to be our second number. And we're going to return n applied to f of x, right? So that's going to give us some f of x. And then we now want to apply n times f to that response. So this is going to expand this f, call that f, that many number of times. So we're ultimately going to add both of those together. So for instance, if we have add zero, one, we're going to get this big old thing. We have zero and one. And we're going to replace all the n's with zero. We're going to replace all the n's with one. And then we say, okay, what's one fx? What was that? And zero fx? So zero return. Second parameter, which is just fx. It's going to be fx. So which numeral is this? One. We just did an addition. Am I crazy? We can do it more complicated. We can add one and two together. So we can do this. We replace n with one. We replace all the n's with two. What's two fx? ffx, right? What's one f of f of f of x? Yeah, f applied one time to f of f of x. Just the same as this. ffx, which is what? Three. Am I crazy? Okay, we need to do multiplication. You can extend this in all kinds of directions that are all super fun. Same thing with multiplication. We want to make sure that zero plus multiplied by one is zero. One times two is two. Two times five is 10. And we can actually use our previous definition. So we want to take in two numbers, right? n and m. And we're going to say take m and add n number of times to zero. So call this function add n this number of times to zero. And that's actually the same as multiplying. So we want to n number of times we want to add n to zero. That actually give us multiplication. So it's kind of cool. Okay, very quickly. So we have zero and one. We replace zero with n with zero. We replace n with one. We have one add zero to zero. So we have add zero and zero, which is zero. And then we can keep going and do other kinds of cool stuff. And see if these examples actually work all with functions. Alright, see you everybody on Wednesday.