 If anybody does that to the video, I support that. OK. So some announcements. So final Wednesday, I've posted a practice homework assignment on Blackboard. So this is not for credit. I don't want you to turn it in. I mean, you can't turn it in. But these are example lambda calculus questions that we haven't covered yet that I've used previously on homework assignments. So you've got this homework assignment. We were a little bit ahead. And also some lambda calculus questions from last year's final. So I think they would be definitely worth your time to do study. Questions on that? We have a large amount of people in here for a project being due today. But a good sign or a bad sign? All right. Be a solution set posted for that practice homework at some point before the sign? I don't know. I don't think so. I mean, I could probably do it. But I'd rather you guys, by working with each other and comparing answers and knowing what's right and what's wrong. See, the knowing what's right and what's wrong is the hard part. Except that, finally, there's no guarantee that we have the same time wrong for the people we use. I see. Yeah, I'll probably do it. Let's say next Monday, let's shoot for that. I'll be able to post at least a solution. OK. Thank you. Cool. All right. So we're talking about lambda calculus. We're right at the tail end here. There's actually just one concept that I discussed a little bit on Wednesday that we absolutely have to get to. If only because if you take lambda calculus and you don't learn about the y-combinator, then you really haven't learned or taken lambda calculus. It's one of the fundamental concepts. But first, before we get there, I want to talk about Boolean logic. So we're going to do logic, maybe touch on addition slightly, but we're going to definitely get to recursion. So we saw that the way we're going to handle Boolean logic is we're going to define a truth value and a false value. These are incredibly arbitrary. I mean, not incredibly arbitrary. By defining true and false like this, we actually get n correctly defining and or and not. We get the proper behavior. But true does not have to be in this way and false does not have to be this way either. Just like in a programming language, right? Some languages, one is true and zero is false. Like in C, there is no Boolean. Or actually, it's more like zero is false than anything that's not zero is true. Whereas in other languages, sometimes an empty list or an empty string can be false. Whereas anything else is true that's not zero or false or an empty list or an empty string. So it really depends on the language. So we saw that we have an and. We can define and like this. So and, just like our normal and function, it takes in two parameters, a and b. And what it does is it applies a to b to f. So let's think about exactly what this does. So we talked about, okay, and should only return true when both a and b are true. It should return true, right? So let's think about this. Let's just look at this here, right? So it's gonna be a, b, f. So the first parameter applies to the second parameter and then f following it. So if you think about what happens if a is false? But remember what did true return? It takes in two parameters, what does true return? Second parameter, so that's the way to think about that. You can mechanically do the beta reduction here and you'll get the correct answer. But trying to develop some intuition for why these things actually work. So if a is false, then what's this going to return? False, it's gonna return a second parameter. Does it matter what b is? No, just like in an and condition, if the first parameter is false, it does not matter what the second parameter is. Okay, so then what's the other case? So a can be false and we know that it's gonna return false, what's the other case? a is gonna be true, so what's it gonna return? a, b, f, if a is true, what's it gonna return? F applied to b. Not quite. So you can think of it like functions, right? If a is true, it takes in two parameters and what does true return? The first parameter, just the first parameter. So it's gonna return b and so now does it matter what the return of and is if what b is? So in that case, if a is true and b is false, then what's the return of the and function? False because it's b and if b is true, then what's the return of the and function? True. Yeah, so we have the case and true true, we call this function, right? And here we're using mathematical definitions here, right? We can completely fill out the, we can write this entire lambda expression without any of these names. So this is gonna expand to lambda b dot ab applied to lambda x dot y. All right, let's look at it here. So we can actually step through this. So yeah, on the left we have the and function. Yes, this is false. Okay, yeah, that makes sense. Where did this come from? Yes, this is the false inside the and function, right? So this is the whole written out and function, right? With no names, no anything. So think about this, can we beta reduce this and function? How do we tell? Yes, expression, expression, and specifically what about, what has to be true about those expressions? Where the left expression is lambda. Where the left expression is a lambda, exactly. So we have function applications here and we know from the precedence rules this technically is gonna happen first and then this is gonna happen. But are any of these rules where the left is a lambda abstraction? Like this ab, is there a lambda there? On the left, no. And if we take ab applied to this, is ab like this, a lambda on the left? No, so this is, we can't beta reduce this function anymore, right? It's just a function definition. Okay, now let's do the whole thing and walk through it. So here we're trying to add true and true. So what should be the results? True, that's where we wanna go. So here, right? I have, first I'm gonna do, we know based on the precedence rules, right? Now here we have application, application, expression, space expression. Where the left side is an abstraction. So now we can do beta reduction step here by applying essentially calling this function with this parameter lambda x dot lambda y dot x. So we're gonna use the exact same techniques we already learned about. We're going to change this. We're going to, inside the body of this abstraction, we're going to substitute a for lambda x dot y x. So this is, replace every free a with lambda x dot lambda y dot x. And so, the thing we have to think about, are there any free variables in this, in what we're replacing? No, no free variables. So we don't have to worry about doing any renaming, right? We only have to worry about renaming if what we're substituting has some free variable. So you can think about what we're substituting in here is completely self-contained, right? It has no global references. So it doesn't matter where we put it because any variable that it refers to is contained within that lambda expression. So we replace this a with lambda x dot lambda y dot x. So now at this point, we actually do have a choice between what kind of application we want to do, right? So there's the outer application. Is the left-hand side of this outer application a lambda? With the two expressions. But then what about this application here? B applied to lambda x dot lambda y dot x. Space B, right? That's another place we could evoke a function. So we can actually do either one, but we'll continue in the way that we were doing it by doing the outer one first. So here, once again, I'm the body of this abstraction. I'm substituting in B for whatever was on the right. And once again, any free variables here? No, so it makes things very easy. I just replaced every free B in here with that exact expression. So I do that. Now I have this lambda x dot lambda y dot x, lambda x dot lambda y dot x, lambda x dot y dot x. So we, just if we look at the behavior, what we think this function should do, right? We know this is a function that takes in two parameters and returns which one? The first one. So it's gonna take in two parameters and return which of these guys? The first one. And what is this value? What do we define this as? True. It's gonna eventually return true. We can actually see this mechanically how this works. So we're gonna do this application. We're going to, inside the body of this abstraction, we're gonna set every x equal to this parameter, right? So we're gonna use our substitution operator. We're gonna substitute every free x in here with this expression. Once again, there's no free variables here, so we don't have to worry about renaming. Why is that? Because we have a lambda y here and a lambda y here. Is this gonna cause problems? So that functions y, not global y. Yes, a, they're lambda, they're meta variables, so it doesn't affect anything anyways. But even if there was a y in here, that y inside the substitution always refers to this lambda y here, right? This meta variable. It doesn't matter where we put that because wherever we put that, this lambda y is gonna be outside, right? So the scoping rules mean that we're not gonna accidentally refer to that. This is kinda what I mean when that expression is self-contained, right? No free variables. So every relationship between a variable here, x, and a meta variable, that binding is constant, right? We know exactly what that is. And it's not gonna change when we do this substitution. So we substitute x for this. So we have lambda y dot lambda x dot lambda y dot x. So we have one more application. So we're gonna substitute in here y for lambda x dot lambda y dot y. So what's gonna be the result of this? This here, are there any free y's in here? No, so the substitution operator doesn't do anything. Essentially we've gotten rid of this parameter, right? It goes away. And now we're left with this, which is true. So the idea here is it makes sense kind of when you look at the function, right? When you look at the definition of and, it was ab true or ab false, right? By kind of reasoning through the ways we defined true and false, we saw that okay, yeah, that makes sense. But if you apply, you know, mechanically like this, and just do the derivations, you should get for true and true true. And then just like when you build truth tables, right? In architecture class, you could do this for every combination. You try false false, make sure do this, it returns false. Try true false, make sure it returns false. And try false true, make sure it returns false. Let's look at one other different case of true and false. Right, so essentially here, we kind of already saw what's gonna happen, right? Just like before, if the first parameter is true, we will return b. So whatever b is is going to be the return of our and function, which in this case is gonna be false. So we can do it at a little bit of a high level here, where we don't actually fill out all of the names, right? We actually know, okay, we can replace a with t, right? And we know that t is a combinator, it's completely closed. So we know we can do this without having to worry about any names, right? So we can replace a in here with t, we can replace b then with f, and we can say, well, what does true false false do? Well, let's expand out true, because we know it's a lambda. So we know we can do something here. So now here we replace x with f inside here, and then we replace y inside here with f. And we know here that this f, once again, is fully contained. So there is no free y. There are no free variables in f, right? So we know that this does not, and then we get back f. Is this a lot easier to think about than the detailed one? But it is the same, right? At the end of the day, right? These are the exact same diagram, but replacing all of these with either false or true, depending on what their value is. Questions on this? And then we can look very quickly at, we can kind of already see where this is going, right? We can see and false true, right? So because this first parameter a is going to be false, we will always return the second value here, and that second value is that constant false that we put here in this function. And so we know just by looking at this, right, that a will be replaced with f, and b will be replaced with t. So this is going to be equivalent to f. False true false. And so we said false always returns what? Second parameter. And so it's going to return false. And similarly, right? So it's just being thorough. We're going through all of these. We have and applied with false false. We get triple false. This first false always returns the second argument, which is false, which means we're good. So do you believe that this is a Boolean, what we just defined an and operator and at least part of Boolean logic? I guess we technically have to do, think, what is it? NAND is complete. You can do every Boolean operation using combinations of NANDs, right? But not just NAND. I think you need something else, right? Wouldn't you also need for an exclusive old? I think you can build up all of those from NAND. Like if you just had NAND gates, you can build any arbitrary logic. But I don't think of the same holds for just NAND. You have to do crazy combinations. So let's do a NAND operator, right? So what should our NAND operator look like? What do we want? So let's think about, we're defining functions, right? What do we want this function to have? What's the signature of this function? If something's true, return false. And then if something's false, return true. So how many parameters does it take in? One parameter and it returns one parameter, right? So we want something like this. Not true returns false, and not false returns true. This is our desired input. This is not the definition. So how can we write this? There is no magic. That's going to be the theme in a sense of wonder. All right. Do we have to do it in one definition, or can we use two different ones? What do you mean? Do you want to have an if statement? Like if t, then return false. It's essentially what we want, but we kind of already have that. I mean, so we want to find some nots, right? We know that not when we give it true should return false, and we know that if we use not and we pass it false, it should return true, right? So how many parameters do we say needs to take in? One, one, one. So let's say we know already that it's going to be something lambda a dot, we know it's only going to take in one parameter. So what is it going to take in? So what was the trick that we used in defining and? When we continue if it resulted in true, then it would check the next one. Yes, we knew the behavior of the true and false functions. We used the fact that thinking about an if statement, true means takes in two expressions, arbitrary expressions, and returns the first one if it's true, but if it's false, it returns the second one. So almost implicitly here we have kind of branching built into our Boolean functions, right? So if we do a here, so a is either going to be true or false. So if it's true, then what's going to be returned? What do we want to be returned? False. So for a, which parameter is going to be returned when we call a truth value? If it's true, which of the two parameters is going to be returned first? First one, so should we put right here after it? False. Then what after it? It's in true. In true. So here, if a is true, it will return the first parameter, which is false. And if a is false, it will return the second parameter, which is true. And so we've basically built this if a, yeah, so if a false else true is essentially what we're doing here. And by creating the true and false values, I mean this is where the, why I say you can't just define them arbitrarily, right? But we get this ifs behavior, right? We get this choosing or deciding between two branches in some sense, right? Because these values that are here, right? Here we're using false and true, but they could be any expression, right? And any expression can be essentially any program, right? This expression here, trying to argue that these expressions can be arbitrary landed expressions. And true will choose the first one and false will choose the second one. So you have implicit branching there inside the way we define true and false, which is super cool. So yeah, we can see that not true is gonna do true false true, which will return false. And not false will return false false true, which will return true. It's exactly the behavior that we wanted, right? And we actually, as I alluded to, right? If we wanna do like an if condition, of if some condition do a, otherwise do b. So we want if true then a, otherwise if false then b. We already have that, true and false already do that, so we actually don't even really need an if function, right? I mean, we can define an if function that looks something like we're used to if condition a or b. But this just returns whatever the first parameter is, c, right? So if it's true, then it will return a, otherwise if it's false, it will return b. It'll do any pay or deduction that needs to be there. So we have basic branching operations. So we have if true a, b, we have our if, we're defining as essentially the identity function, so it returns its first parameter and only parameter. So when we return this, we're gonna get tab and that's gonna return a. And if the condition is false, it's gonna turn fab, which is b. We have a super basic if condition, but we don't actually, if condition's not really doing anything, right? It's the Boolean logic here, the way we define true and false that's doing this. Okay, questions on logic? So you convinced logic is possible? No. No? Which graph? Which graph? You're probably the wrong major then. Do you think logic is which graph in general? What about specifically this logic? Yeah, so you could do any arbitrary logic here, right? Anything, so we did and not, so we know it's complete. Very similarly, you can define x or functions, you can define or any logical operator you want, you can define using lambda calculus and using these true and false values. You can change how you define the true and false values and define a whole different system of logic, but it can still work the same way. I'm gonna skip ahead. I really wanna cover numbers. All right, how important are numbers anyways? I'm pretty sure they're pretty important. I don't know if there's a point in this one. Logic, I don't know. Okay, let's do this. We'll go over numbers very quickly. No, no, no, we're gonna do, okay, because we just did logic, right? We did logic and we can do true false branches, right? So already, if we can do logic, we can do true false branches. We pretty much have most of our language, right? Just like a Turing machine has a program, depending on what symbol it reads, it can move to a different state, right? So that's essentially a branch, right? Just like here, we have a branch, we define an is statement, which will choose between two different expressions. Okay, we will have arithmetic where we can do numbers, we can compute on numbers, but just like a Turing machine doesn't have any intuitive concept of numbers, right? It only writes out ones or zeros. Very similar thing here, arithmetic, right? No. Okay, weird, it looks weird. All right, just like that, right? We're gonna do it in a different way, just like we did with true or false with using functions to define the numbers, which is very similar to using ones and zeros to define numbers, right? Just different ways of encoding that information. But are we actually Turing complete with this? We have branches and we have arithmetic. Are we Turing complete? Can we do everything that a Turing machine can do? Yes? Are you just shouting out an answer? Yes. Yes. Are you just repeating the same answer again? I was just talking last day. I'm sorry. Well, think about it. I mean, this actually gets to something very big. This is why you are computer scientists and not getting a degree in programming, right? Because you're thinking about computation, right? What does it actually mean to be able to compute something, right? Can you write a program with just branches and if branches and arithmetic? Don't you need go-tos? Why do you need go-tos? What specific fleet do you need? Well, just looking back at the project definition that you need go-tos to go back on today. I mean, you can make everything with ifs and go-tos. But what did go-tos allow you to do? Go-tos allow you to repeat parts of code. Yeah, it's a loop, right? And to loop an undefined number of times, right? Exactly. So that's what we're missing here, right? We have no loops. Loops, we have no way to execute something again, like to do an instruction against here, right? Think about a program, writing a program that just does branches and has no way to go back is actually not a turn-and-complete program. I mean, that program is fine, but that language is not turn-and-complete, right? You can't write a program in that language to do something like, how do you calculate a factorial, right? Because you write a program that takes in a number and calculates a factorial with only branches and arithmetic. Yeah, for a finite size, yes. Exactly, you can only write it for a finite. You could have all these huge if conditions of if n is less than 10 or whatever. I mean, you could write out all the cases, but you can't handle the general case if I give you a number n and you give me factorial of n, right? So factorial of n, we have our base cases, right? We have factorial of zero is one. Factorial of n is n times factorial of n minus one, right? And so if we're writing this in C, like we've been studying this whole semester, right? We define a factorial function, we check. If the factorial is zero, we handle the base case, right? We always have a base case. If it's zero, we return one. Otherwise, we return n times factorial of n minus one, right? And this follows very similarly to the mathematical definition of what a factorial is. What kind of a definition is this mathematical definition here? Recursive. Recursive, why is it recursive? What does that mean? It includes its own definition and its definition. Yeah, it calls itself, right? It's terrible in a dictionary. You don't want to define a word with itself, right? But here, it's one of our basic primitives that we use in programming. We want to be able to call the same function with itself. So, assuming that we can do addition, we can do multiplication, we have a way to do those. And assuming that we have an is zero function, so some function that tests if it's zero, and we have some way to do n minus one. So in this case, we're going to call that the pre-assessor or the pre-function, right? So every number has a pre-assessor and a successor, right? So it's that number minus one. So if we want to follow this definition that we have here, we want something that looks like this, right? We want to say factorial, and it takes in one parameter n. So we have lambda n. Then we have our if, right? So what's the condition of our if? Well, if n is zero, if that's true return one, if it falls for multiply n with factorial of n minus one. n minus one. Great that this is the straightforward application of that program we just wrote in C. But what's the problem here? Expand. Yeah, I've been using, we've been using equality, right? By saying true is this. We're using the mathematical definition where that we're saying true is defined as this. Which means everywhere we see true, we can replace it with this, right? So we can always get rid of every name that we've ever seen. We can get rid of if, we can get rid of is zero, we can get rid of mult, we can get rid of predicate, right? And write out their exact lambda expressions with no names. But how do we do that with factorial if it's used inside of its own definition? It would expand forever just like the type system, right? And it would never actually be able to build a concrete type. We'd never be able to have an actual function here. So we cannot write this function in this way. But the crazy thing is this does not mean we can't do recursion in lambda calculus. This is actually an idea that's pretty insane. So you want to be able to call a function, but you don't actually have a name of that function, right? This is an anonymous function, right? Factorial has no name. We cannot refer to it by name. But we can still get recursion here. And so it comes back. So I mentioned about combinators, right? No lambda expressions with no free variables, right? There are actually a bunch of alphabets soup of combinators. One of them is the Y Combinator. Anybody notice that term from another context? No Y Combinator, that specific expression. Nope. No? No startup people or entrepreneurs in the audience? Yeah, they're a startup incubator in Silicon Valley. They were started by some people who founded an original company called Bioweb in the early dot com in like 98, where they created, they built a website. Like people could create automatically their own online stores. It was called Bioweb and they got bought by Yahoo for a bunch of money. And that became Yahoo stores. Then they decided that they wanted to get into startup investing. And so they did a, but they didn't want to go to VC, the venture capital model. So they did this kind of, they were the first ones to do this incubator model of you come from the summer, we'll give you, I don't know, 10 or $20,000 for X percentage of your company. You'll work fully on your startup. We'll give you access to our network, everything. And then you can, then after that, then you go out and get money. So it's like trying to fill the gap in between yourself funding your startup and when you get the like multi-million dollar venture capital. Anyways, they're incredibly successful Airbnb. Anybody know about them? Dropbox. Those are all Y Combinator companies. There's actually a huge list of Y Combinator companies. So they named, that's what they named their startup incubator is Y Combinator. And it is directly from Lambda Calculus. So if you are ever going to get, try to get money from these people, you should definitely be aware and know what this is and how it relates to programming. So we're gonna find this Combinator kind of crazy. So the Y Combinator is lambda X dot lambda Y dot Y applied to X, X, Y. And on the right we have lambda X dot Y dot Y, X, X, Y. Yeah, so it's the same thing repeated, right? The question is what does it do? So let's say we're applying this to something foo. So we have the huge, we have the huge lambda Combinator, the Y Combinator, we have foo. And so let's do this first application because we actually have an inner application inside the Y Combinator, right? So we have that patient of this to this. So how many parameters is this one gonna take? Two parameters. So what's it gonna do with the first parameter? Replace X's. Replace the X's with this. The X's are twice, right? Notice how the Y Combinator has two. So this is essentially propagating the Y Combinator here. These X's are gonna replace with two of this parameter which is the Y Combinator. So this will be the Y Combinator again. And so foo is the second example. So what's gonna happen with foo? It's gonna be replaced with Y. It's gonna be replaced with Y. So we're gonna have foo here. But what is this X, X, Y in here? It's the Y Combinator in here. It's not exactly, it is the Y, the X's are the Y Combinator, but what's the Y here? Foo. Foo, which is this, what we started with. And then what happens if we expand that again? The X, X, Y. The exact same thing again. So we're gonna do the replacement, replace all the X's in here that we saw. So these X's are all replaced with this guy. We have foo. Then we replace all of the Y's with foo. And so we end up with foo applied to Y foo. But is this foo a name? No, it's not a name, it's a lambda expression, right? That lambda expression gets copied there. And then what happens when you expand this out? What? You get Y, you get foo, Y foo. And then inside there you get foo, Y foo. And then inside there, foo, Y foo. Does it ever stop? No. No, so we get. It stops when foo has some kind of base case. Y foo's. Yeah, so we have to code it correctly so that it has a base case so it doesn't continue forever like this. But here we're getting a function to basically keep calling it out, calling itself, right? We've essentially done recursion here. Using this crazy Y combinator. So what we can do is we can actually define our factorial function. So instead of like this, right, where here we can't actually write this definition even though this is what we want, right? This is our goal state, this is what we want the factorial function to be. But what we can do is if we set the factorial function first to return Y, the Y combinator applied to that function that we want. But what changed about the function? Right, but how many parameters does this new thing have in here? Two, right? So what are we passing it as the first parameter here? Itself, exactly, yes. The function that we want to call is gonna get passed in as a parameter and the Y combinator will make sure that what gets passed in is going to be this function again. And then if we call f on predicate of n, we'll do predicate of n and then we will do this again and we'll expand again until we hit the base case where is zero and move return one. So by changing this, by saying, okay, we cannot name factorial, but we can accept it as a parameter, we can call a parameter f and we can put it as part of an application here. So let's see what this looks like with factorial of one. So we have the Y combinator applied to this guy which is the one we originally want with another parameter applied to n, or sorry, applied to one. So we do the Y combinator step which first gives us lambda f dot lambda n so that the expression that we passed in applied to Y of lambda f dot lambda n is size zero one, multiply n by f predicate of n to one. Expanding that out again, so replacing now the f with the new function including the Y, right, the Y combinator, we can reduce f, we can replace all the f's in here. So now we have something that we can reduce, we have an n and we can replace it with one. And so we say if one is zero, return one, otherwise multiply one by Y of the same thing again, right? One with that Y applied to the same thing again. So we do this again, we say is, is one zero? No, it's false. So it's gonna return the second argument here, so it's gonna get rid of this first one. So we have multiply one by whatever Y of f of n applied to the predicate of one. So we go through the same steps again. That's going to go out, give us here where we now have our Y combinator inside here. Now we have the predicate of one, so we replace n with the predicate of one. Predicate of one is zero, so we replace n with zero. Now we have is it zero? Yes, it's gonna be true. So we will, this whole thing, now we don't, are not going to evaluate the false branch. So we don't care that this is going to repeat infinitely, right, because it goes away. It's like an if statement. Here the statement is true, so we take the true branch, which is one. And so this whole thing reduces to one, you multiply one by one, you get one. So now we have Boolean logic, we have arithmetic, we have loops, we have hit, turn completeness. But do we actually have numbers? Well, it's just like to you. So what we're gonna do, we have to define, just like we did true and false, right? We're gonna try and define each of the numerals to define zero as, well, actually, how many parameters is zero taken? Two. Two, which parameter does it return? The second one. The second one, which is similar to what? False. False, yeah. We're gonna define it the same as false, which is kinda cool. We define one as lambda f dot x of f applied to x. And so we'll define the numbers based on, whatever number you give me, I will apply f that many times in the body. So two will be dot f, f, x, three will be f, f, f, x. Can we do that? Sure, is there any, I mean, is there any difference between encoding numbers like this versus ones and zeros? You can actually, well, you can do numbers with all ones, right? I mean, you don't actually have to have a zero makes it more efficient. We need to have a way to distinguish between a one and they're not being a one, but you don't actually need a zero character, right? And four would be four fs. And so the way to parse this is we first apply f to x, we apply the result of that, to f to f to f, repeated infinitely. So the idea is if I have four a b, this is going to result in a a a a b, right? First parameter being done four times. So I have numbers, right? So I have some concept of numbers that I can say if, can I look at, if I do some calculation, can I look and can I see what does this, is what does that result functionally equivalent to? If it's functionally equivalent to any of my numbers, then I know that that is definitely a number. Everybody agree? But the question is how do I move from one number to the next number, right? That's actually one of the key properties of numbers. You give me any number, I can always give you the next number. It's pretty trivial, right? Maybe you never thought about it too deeply, but you, every number has a successor, right? The successor of one is two, the successor of two is three. But we can actually define this using lambda calculus. So this looks kind of tricky, but it looks like it takes in three parameters, but it actually takes in one parameter. We want the successor. So let's say I give you some, a successor function. What's the type there? What should it take in? What should it return? A number and a number. A number and a number. Should it take in one thing and return one thing, right? So it takes in one thing and, and it returns something of the form what? Lambda f dot lambda x, which is a function that takes in two parameters, which is the same type as the numbers we're talking about. So you'd think if it takes in some number and it returns arbitrary numbers here. So the question is, okay, does this actually work and what does it do, right? So what is this function doing in here? So what's n here? The initial number, right? The number that we pass in. So if we pass in, what was our numbers kind of, what did they do? They take in two parameters and what did they return? So they take in two, an f and an x, and what is the body? Not quite recursive, right? It's a fixed number of times. But it takes the first argument applied that many times to the second argument, right? f of f of f of f of f of x, right? However many times. So if n is one here, it takes the first argument and applies it to the second argument once. So it's gonna return f of x. Which is a static one. And then we add one more f to it, which is two, f of f of x. And then we can do the successor of that. Two f's would be f of f of x and f of f of f of x gives us three. And similarly with zero, the successor of zero. So here we pass in zero for n. Zero returns its second argument, which is just x, okay? So it's gonna be lambda f dot x dot f of x, which is one. And we can do this with one. So we have the successor of zero as one. And based on how we define the numbers, we can do this. We can do one, we can say what's the successor of one. We substitute in one here. And then f is going to be applied to x that many number of times. And this time it's gonna be one. So now this is the same thing as f of f of x, which is two. So the successor of n is always gonna return n plus one. Based on the way we define the numerals here. And we can generalize this to, you can actually do this for addition. So there we were taking in one number and returning the next number. Here we're taking in an n and an m and we are adding them together. I'm not gonna go into the details here. I'll briefly go over it in case you're interested, that you can show that you can actually do addition like this. You can even do multiplication. So we can multiply two numbers together using our add function, right? So multiplication is just add something to something else that many number of times, right? So we can use multiplication here. And so here you can do subtraction and all kinds of crazy stuff with numbers. So you can completely do it representing everything just as functions, right? We have loops, right? We have logic, we have arithmetic, we have loops, everything you need for a super cool, fun programming language. I see you all agreeing with me. And now, saddens me. We've reached the end of the course. Yeah, so I'm gonna touch down a little bit. Really the purpose of this course, and honestly the purpose of your computer science degree, is to dispel any notion in your mind of magic, right? Computers are not magic. Trust me, I'm a huge Harry Potter fan. But magic is for Hogwarts. Computers are not magic. There's absolutely nothing magical going on, right? It's all systems, it's all, we've studied compilers and how compilers work and how languages work, right? The magic is understanding them. No, it's not magic, it's just hard work. Just like anything. So what you should take from this is that you have the faculties and the tools to understand these systems, right? It may take time, it may take effort, but you studied all about how a compiler works, right? You know all the steps that are involved in a compiler. Now you know if GCC is throwing some weird syntax error, you're like, oh, it's probably because it's expecting some token here and it got a token that it didn't expect and that's why it's throwing this error so I should figure out maybe what's going wrong, what's going wrong, right? And just like you take operating systems so that you understand operating systems, there's no, we built these things, right? Humans, we built these. You need to be able to understand them and to know that there is no mysteries, there's no magic, it's all computing all the way down to the silicon, so. Go for them, do awesome stuff. Go for them, do some stuff. There it goes. We will miss you.