 So we are ready for the fourth lecture. We'll continue with building on our language. And today is Thursday, which means no laptops in the room. So if you badly need to go on Facebook, I'm afraid this is not the room today for that. I want to point out that there is a homework, optional homework, assigned that goes with PA1. It was a regular homework that was collected last time the class was taught, but it was considered to be too much work. So now it's optional, which probably saves you the time that you don't need to nicely polish it and submit it. But it will help you if you solve it before you start doing the sugaring in PA1. It's a far real world problem that will point out some limitations of Python. You'll have fun looking at it. And I'll start as soon as laptops are gone. Please. The homework is optional, so it's not due. But you should probably do it before you start the sugaring. Yes, please. The solution is posted. As soon as you are ready to see it, let me know. I'll send you the link. Yes, you do because you will do better on the project than the exam, so you will. OK, so it looks like we are ready to start. And here is the language that we ended with last time. And nothing sophisticated. In fact, we want this language to be as simple as possible so that the interpreter that runs it is simple, easy, and the rest is done by just rewriting the tree locally, so-called the sugaring. But before we go on, I want you to look at this language and tell me what you seem to find funny, and we may want to change it a little bit. So this is just syntax, which means telling you which programs are legal. It doesn't tell you the semantics. It doesn't tell you what these expressions evaluate, too. But if you look at the syntax, you will see what programs are legal, and it should all of a sudden become obvious to you, oh, I will have to evaluate that funny program, too. And I don't know how I would evaluate it. So you may want to change the grammar to prevent some programs from being legal. I see opportunities for two changes that would simplify the interpreter. So can somebody give me the first program that perhaps we don't really need to write, but it would complicate the interpreter, maybe? OK. Well, would you be able to? How about if you do? Well, I think you are close, but I want to let you find a program that we may not want. So operators on lambdas might be tricky. You may not want to do lambda plus lambda since plus may not be defined. But there might be some operator that you may want to define over lambdas. Can you think of an operator that would actually make sense over lambdas, which are functions? You may want to compose them. So I write lambda lambda, and it really means apply one, take the result, plug it in, into the other, sort of chaining them. So I don't want to exclude that, because that might be useful. And after all, lambdas are expressions that evaluate two values. They are first class functions, meaning their values can flow through the program. So applying an illegal operator on a lambda we'll have to take care of, but we'll do it in the interpreter as we evaluate bottom up. So that we cannot quite simplify away. Do you see something strange? So how about if we do, I don't know, x plus x equals 7 is this legal in our language? It's legal, right? After all, in our language, this is an expression. Expressions evaluate two values, statements do not. We have defined assignment to be an expression. So therefore, this is legal. Do we want to keep it in the language? Do we want to prevent such things from being legal? Well, we could use an expression in a Boolean operator. Just let's assume that we define one of the operators to be an or or, and now we could use a Boolean right there. So I think that we want to do, and we still can do that. But what about the assignment? What do we do with the assignment? Do we want this piece of code here? We want this to be a legal program. Yes? Do you see something potentially ambiguous in running this program? So now the order of evaluation is a problem, right? Because if you evaluate this x first, and then you evaluate this expression, it's a different value than when you evaluate this second and this one first. Because x is now changed in the middle of the evaluation of the expression. Exactly. So we cannot eliminate it entirely. That's an excellent point. That lamb does have side effects. They could modify global variables, and we could do the same thing, right? But at least we prevent some obvious things that programmers could abuse, such as put assignments in the middle of the expression to save one line of code and burning themself when the interpreter evaluates things in a different order. Does the left side first or the right side first? So I'd say let's put the assignments among the statements, since they don't really need to return values. Now we could debate it that this is not a perfect fix, does not really make it perfectly safe. There is another thing that we may want to move into the statements. It does not quite produce a value, maybe. Maybe the def should be sort of a top level construct that does not want to appear inside an expression and will move it inside statements as well, just to be a little bit more disciplined about both programs we write. So we end up now with a grammar that has statements that do not really produce a value like this one, the assignment. The expression could produce a value at the top most level and nothing happens with it. It may only have the effect of the side effects. So you call a lambda, it does its side effects. And the call, of course, is an expression, but you make a call at the level of statements where several calls happen next to each other. The rest are expressions that actually produce values that evaluate it. So we don't really have a deep point here except that some constructs don't need to produce values. And we are now reminded what our language is. That's it. Now, let's see what we did last time. We had a language where we have functions which are closures in the sense that they carry their environment with them. They have a pointer to the parent frame where they look up their variables. And we could use them to define if and while constructs, which are just functions that are high order functions. High order means that they expect as arguments other functions. And so we could, by adding just one little construct, if then else, we could support if and while, which again are functions. So they reside somewhere in the library. You can think of them as you run them through your interpreter before you run the program so that the interpreter reads in these definitions of if and while. And what do they do if it receives a conditional and a body which is a closure, a function? And depending on the conditional, it either evaluates B or this empty lambda. How do we know it evaluates it? Well, the result of ITE is called. You can see it right there. While it's almost the same except you evaluate, you pass C wrapped in a lambda, you evaluate it here. And depending on T, you either execute the body or you recurse OK. And then you recurse into a while. Did I do it right? It looks like there is a mistake in my slide. Can somebody see the mistake? So when do we call while recursively? We need another if statement here. If T, then we do a while. But that while needs to be wrapped again in a lambda, right? Since we need a lambda to pass in. So I need to fix this. So the disugaring is not as nice as we would like. But at the topmost level, you can actually write programs with while, with that high order functions reasonably well. You write the conditional here. You write the body in here. And this is your while loop. There is some extraneous stuff, the headers here. But it's not so bad because if you look at jQuery, which programmers love, it does the same. When it specifies some piece of code to run, say this one, it wraps it in a lambda and passes it into this high order power function. So programmers can live with this. No big deal. But we'll make it nicer in our project. And what we'll do, we'll give you a parser which reads this beautiful syntax that you are used to, and produces an AST, which contains a node for this while construct. And you will then, by rewriting the tree, produce an AST like this, which does both. It makes a call to that library while function. Essentially, it does nothing more than taking the E, wrapping it in this lambda, taking this S, and wrapping it in this lambda. And turning this while node into a call to the while loop. So your disugaring just wraps them into lambdas and makes a function call. And I know there was a question that somebody wanted to ask about while disugaring. So this might be a good opportunity. OK, so this while here is incorrect, clearly, since I forgot what probably you are asking about. But let's write it correctly here. So what would while do? So while is a function that receives the conditional, a lambda, and a body, both of them are lambdas or closures. And now we evaluate C by calling it. We store the result in this temporary variable. Introduce just for the purpose. And now you can call if with the result of T and you pass the body. And if executes the body if T is true and doesn't execute it if it is not true, all right? So far so good. And now we want to call while recursively, but we want to call it only if T is true. So what do we do? If T, and now we need to put the body of if, should this be lambda, empty argument, and what will go here into the body of just a while? This will be a while. And while has two arguments, what are they? This will clearly be the same body we execute. And how about this? Is this going to be a T or a C? It needs to be the capital C, right? Because it is the lambda which will be evaluated again. And essentially, the while loop asks in here in the next recursive iteration, is C still true? If yes, it does one more iteration into the recursion. So that's it. Now there is a problem with implementing a while loop like this, right? What's the problem? Do you see some big efficiency problem? The memory issues of recursive calls, right? That you call it, the stack will be as deep as the number of iterations in the while loop, right? So there is an optimization that deals with it that some interpreters actually implement. That's called tail recursion elimination. If you look at this recursion, then the iteration, the recursion stops. What happens when the loop, the while loop, decides not to iterate, not to recurs anymore? Do we do any useful work on the way back from the recursion? It just returned, returned, returned all the way to the topmost level, right? And it's easy to detect that when you call a function recursively that this sort of so-called tail recursion where nothing happens when you tail out, that no work will be done. And you actually don't need to push new frame on the stack and then pop it, and pop it, and pop it. You essentially reuse the existing frame, and therefore there are no memory issues. You should feel free to implement it, but we don't ask you to. And so if you have wrong running loops, then yes, you may run into this problem. But it's easier to just implement in your course project. They essentially ask Python for a deeper stack. Exactly, right? Sometimes it's not easy textually to detect that that's the case. But essentially, if after the recursive call the immediate statement is a return, right? It is the last statement. Then you know it would be a tail recursion because when the call returns, you would return, and that's so-called tail recursion. So does that answer the question about while? OK, perfect. So what we'll do today, we'll first look at iterators a little bit more. We'll look at the four constructs. Then the tables, which are dictionaries, comprehensions, coroutines, lazy iterators, how to compose them, and probably not regexes. Maybe we'll leave those for later. So tables, tables are nothing really but hash tables and dictionaries. And here is an example how we'll use them in our language. You create them with this so-called literal. Literal is a constant that creates an empty table. Table as a hash table. And now you see what happens next. I'm putting a value next to a key. So here is a key and a value. And look what I'm doing here. I'm using the hash table as an array, just using the numeric index as a value and printing its value here. So in a sense, it's a very versatile data structure that we can use here. So if you look at the operators, we need something to create an empty one. We need to read a value given a key. We need to store a key into a table. So the first question is, why do we have, when I describe these operations, a general expression here? Clearly, you want one here. I do want an expression, arbitrary one, that evaluates to the index, the key into the hash table. So why am I allowing arbitrary e on the left? Function could return a hash table. In fact, you would want to write something like x of y of z, just have deeply nested index. So what other operations we may want in order to really make dictionaries useful? I'll wait for the gentleman with the laptop to answer. Thank you. So what other operations we may need? So somebody else, what do we do to make them more useful? Let's try, if you can, by saying maybe a scenario in which we would like to use dictionaries. And then what operations we need for that? Well, let's start with the operation and we'll invent the scenario. OK, so exactly. We could decide that these hash tables are not only right, but you can also remove elements. So essentially, remove the key entirely rather than changing its value. Perfect. So we want to have a deletion operator. We could get by without it, perhaps. We could say, this is not allowed. And when you need to do it, you will copy the dictionary into a new one without it, sort of less efficiency. But there are languages that don't allow changes to the data structure, so-called immutable data structure. And then you have no choice. So that's great. A few more? OK, right. So in other words, you would like to have a different way of constructing it, where what you construct is a table that is not empty. So that would be really useful. In fact, we'll have that in our language. The question is, do we need to do it through, by adding anything to the interpreter, or could we do it by the sugaring? Right, because we can do it with the sugaring, because on top of these expressions that we have here, we can already do that. Delet we cannot quite do, because even if we copy the data structure to create one without the key, somebody else may be pointing to the one that we want to delete stuff from, and that we cannot undo. So delete is truly unique. That cannot be simulated on top of this. Fantastic. A few more, maybe? All right, get the list of all keys. Why would that be useful? Exactly. So that you want to iterate over all elements. If I give you keys, you can then iterate. You can also do another useful operation when I give you the list of keys. What could you do? What was that? Oh, yes, sorry. OK, so there is a typo. Thank you. So if I give you a list of keys, what other useful operation you can build? Through the sugaring rather than natively. Say membership, right? You can then ask about membership. So here are a few other things that you want to discuss at home and think about it. The various corner cases such as if the key is not present, what return value do you produce? If your language does not support exceptions, then you need to make some sensible choices to what value you return. So I'll leave them here for you to think about. And another question perhaps. I'm extending the language with tables, essentially suggesting that we'll teach our interpreter, our base language, in other words, we'll support these tables natively. But do we have to do it? Could we emulate them on top of the constructs that we have? They would be slow. They will be slow anyway because you're doing it on top of Python. So that is a different story. They will be slow. But how would you emulate them? Do you even need closure? Do you even need that? You can think of a closure could represent the hash table. And in order to find out whether a key is bound to a value in that closure, what do you do? You call the closure with the key. If that closure stores the key, then it returns the value. If it doesn't, it has sort of implicit linkage to another closure which it calls. Remember that closures have pointers to pair and frame. So you can essentially link closures together. And you call a closure. It does some work. It says, no, I am not that key. And it will then call another closure until it reaches the end. So you can sort of build a link list out of closures. I believe there was such an example in the 61A book that you did. If it is not clear, try to figure out how you would fake these tables with just functions. It would be harder to make them mutable. It would be harder to support a deletion operator. But otherwise, you can do it. But we won't do it like that. You'll essentially implement these tables on top of Python dictionaries. All right. So much about the ASB. We really need them only to do the stuff that we want to do for the rest of the lecture. And the first construct we want to build is a four construct, essentially an iterator. So what do you think this one will print? It just prints values 0 to 9 or 1 to 10 or 1 to 9. It's a matter of definition how we define it, right? So let's assume it prints 0 to 9. But honestly, I don't remember what we assigned in the language. It doesn't terribly matter. Now, what would this expression here, what would it return? When we evaluate whatever is there in four, what kind of value will be returned? There isn't a single good answer. There might be multiple. So what are a few choices we have? And which of them you think will give us more flexibility? So it could return a lambda. What will this lambda do? What will we do with it? Exactly. In each iteration, we call the lambda and it will give us the next value. So that's not the only choice. What would be another choice? What could that expression there return? It could return a list implemented as the hash table, right? The hash table index by 0, 2, 1, 2, 2, 3. And OK. So both of them are reasonable. We'll go with the lambda. And here is the implementation of it. So here is the iterator. When we call it, it returns this lambda. And this is the lambda that is called in each iteration. Now, what it does, it increments the i and it returns the i. And so this for loop, it always brings a different value of x. So this i flows here into x. Why do we need this null here? Could we just have an if without the else statement? Am I just being pedantic? Or would it do something wrong if I did not have the null? Exactly. So null is our protocol that we have with the for construct. When we call the lambda in the for and the lambda returns null, the for construct knows I'm done with the loop, I'm exiting. So this is essentially protocol for saying the loop is done. And that's OK. So the question is, if the lambda has no expression at the end, essentially this is one. This lambda has just one statement. And statements don't return functions. So then we do need to have some sort of agreement. What will the lambda return? A returning null would make sense. Just defining that all such lambdas return null so that interpreters cannot differ. And programmers are not surprised when they port a program from interpreter to interpreter. And in other words, we can test your interpreters. All right. So the i is an important variable. i is defined here and used here and here and here and here. What if I have a for loop that has another for loop inside? I could have 4x iter 10. And in the body of that for, I could have 4y iter 5. And I could nest it arbitrarily. Each of these iterators needs its own i. So do I have a bug here, or is this going to work? If I have three nested 4s, each of them has its own call to iter. We need clearly three different acts. So is this going to work correctly, or do I have a problem? Or we are relying on the beauty of what to make it work. Exactly. So if we have three nested loops, we'll make three calls into this function. Three copies of i will be created, one for each fresh lambda for the iterator. And they will stick there together. They will sort of exist separately during the duration of the triple loop nest. And each closure, in this case, the lambda that we call in the iteration, points to its frame. They have a separate frame, each with a separate i. So you should be able, for example, if it comes to the exam, to draw the environment, which is the tree of frames if we have three such nested 4s. But by the time you debug PA1, you'll know what it looks like. OK, so this one will probably print 1 to the way it's written. It will print 1 to 10. Excellent, right? So that's a matter of definition, but the way I implemented it would be 10, right? Good point. OK, so let's go to more fun stuff. So how would you desugar it? Well, so you have a for construct that has a variable here, so an id. Here you have some, any expression. It does not need to be a call. And what do you do? You create a temporary variable. The name is invented by the interpreter, by our compiler. You evaluate it once here. This is the first, right? The value for the first iteration. You check whether it's null. If it's not null, you call it again, and then you go to S. Is this correct? Not correct. This is any id, any expression, not just a call. So is this a correct desugaring or not? Exactly. So we'll skip the very first value. And in fact, if you look at when the body is executed for the first time, it gets the second value rather than the first value, right? So this indeed is incorrect. This needs to go at the end of the loop. Small things, but we'll all make such bugs. So be careful how you desugar. Now, a little bit about desugaring. Notice what we've done here. We took this construct, provided by the parser, and the body is entirely copied here. This rule does not look into the body. It just copies it over. And this is what we want from the desugaring rules. We do not want them to peek inside the body of loops and say, oh, I desugar this way or that way, depending what's in the body. These rules need to be simple, modular, say, this rule will work for arbitrary body of the four. You don't want to peek in it. Now, does it mean that the body of the four is not going to be desugared? It needs to be desugared recursively, too, because there could be other four statements and wild statements in it. We're just saying that in this particular rewrite, you are not peeking inside the four. This is essentially what this is saying. So this is your little homework where you need to write an iterator for a table as an array. So given a table that is used as an array, as here, you want to iterate over it and print here 1 and 2. And so this function, really, is called an iterator factory, because it does what? It returns an iterator. And so for the table, it's not much harder than it is for the counting iterator that we saw before. Comprehensions. What are comprehensions? Now we want to do things that go sort of one level above four loops. We want to write a map over iterable elements. So we would like to be able to do this. This, just like in Python, it shows we are iterating over some collection. Which one? Well, those returned here, OK? We bind every element to v and then evaluate this expression and create a list. There is a concatenation of these values. So how would we desugar this? So have a look at this and try to see whether you see a problem with this desugaring rule. Now this is a much trickier question. So I will be really impressed if you spot a problem here, because in fact, this is the desugaring that Python uses for its comprehensions. And it confuses, OK, all right? v at the end is going to be the last value. But you could say, no, the way this four is desugared. Because we have desugared it to this, and then four is going to be desugared further, of course, in a while. The scope of v ends right there, so nobody has access to v. So that's OK. So no, I think that would be fine. In fact, it turns out that nothing is really broken at this step. It is how we desugar the four, but make things really strange. And so this is what the optional homework is about. It really will help you understand the subtleties of when you implement one construct one way versus another way. And so there is a funny story that you can read about in the homework. I won't give it out by telling you a little bit more, that famous professors in programming languages play with Python and say, I like the language, except sometimes it's crazy and sometimes it's broken. And they write this piece of code, which is copied in your homework, and say, lambda is broken. And other people say, no, no, lambda is not broken. Something else is broken. You see that the way they implement it, Python does not really let people understand easily what's going on underneath. And the way we have been desugaring things so far is along the lines of what Python does it. And so it could lead to the surprising behaviors that people observe with Python. And one of them says, no, it's actually nothing is broken, but when I counter the strange behavior for the first time, I threw up in my mouth a little. So I think you want to look at the example and see the polemic that people have about the behavior of Python. And it all boils down to the desugaring rules that we are talking about here. It's quite amusing, and it's also nice to see what you need to do to desugar it right so that people are not surprised by the behavior. So I'll leave it to you as the homework. And but one thing, as you are defining the desugaring for comprehensions, we just need a slight modification from what I've showed you. You want to make sure that it works for nested comprehensions, right? Essentially, these are nested loops, but you can write them as nested comprehensions. And a funny way of remembering how these nested comprehensions work is to read this line. So you want to read them right to left. So this comprehension starts here and ends here, and here is one, four, here is another one. So you read this one first, and then you read that one, which means this is the outer four, and this means this is the inner four, because this is the right one, and this is the left one. I think that's all we need to say at this point. So if you remember what we have built, it started with lambdas, then built if and while for an iterators and comprehensions, and the stack of abstractions we have is growing nicely. But to do really powerful stuff, we need to look at lazy iterators, okay? Imagine I want to do the following. I want to write a procedure into which I pass an array or a list, whatever you want to call it, and I want to print all possible permutations of the elements in the list, okay? Does anybody know an elegant algorithm for doing this? Or attached to the beginning, maybe attached to the end? Oh, yeah, but essentially you do it recursively. You take one element, recursively find all permutations, and for each of these permutations, you want to somehow put the elements, okay? Well, let's look at the code. It's right here. So we get a list of certain lengths, right? At the top most level, the n is the length of the list, okay? And if we are down to a single element, then there is only one permutations. We just print it, okay? But otherwise, what do we do? For the lengths of the list, we do what? We swap the i-th element with the n-th element, right? So this is swap, so we swap i and n-th elements, okay? And now you swap them again, and then we recursively call the element on the smaller size. Essentially swap the last one with the i-th for all i-th, and then recursively do it on a smaller chunk of the array. So as you go down the recursion, you're computing permutations on smaller and smaller and smaller chunks of the array. So how exactly this works? You may need to wanna look at it and play with the paper and pencil, but it's an elegant algorithm that prints all permutations. The real challenge for us is not the algorithmic. The real challenge is that I now may want to write code like this. So I want to turn this permutation generator, which was recursive and it always printed at the bottom of the recursion, and I want to turn it into an iterator that I can use in a loop like this. So how would I do it? So an obvious solution, I'm going back to the previous one, is replace this print here with something like list append. So I run this permutation, I collect all permutations, stuff them into a list. And then in this loop, I will have this iterator essentially return the list and then iterate over it in the loop. It's not very efficient because there are exponentially many permutations and maybe I'm only interested in a first few of them. So why would I construct the whole list when only a first few of them are needed? So I want to construct these permutations lazily. Right, I want to write it in such a way that here I have the loop which iterates. Here I have the permutation generator, which is recursive, and I want to start the loop. The permutation generator goes all the way down to the recursion, and somehow I want to hand the value back to the loop so that it can do one iteration of the loop. If it decides to do one more iteration of the loop, I want to return to the permutation generator, which is now where? At the very bottom of the recursion, it has its call stack here, full. I want it to do a little bit more work, hand me the next permutation, go back to the loop, another iteration, go back a little bit of more work in the recursion without, of course, popping all the way up and go back to the loop. So how do I do that? No, this A actually prints entire array. Prints A0 to An. So it does print the entire thing, but the recursion, as it goes down, it considers only a smaller and smaller subsequence of the array. So why would our existing iterator fail to support this, right? Remember what our iterators do. Our iterators are lambdas that the loop iteration calls. The lambda is invoked, returns the next value, loop does whatever it wants with it, and the next iteration, it calls that lambda the iterator again. Cannot we just use that for the purpose? Exactly, so not only, so your answer is correct. The whole work goes down to the iteration and getting the next base, next permutation is just perhaps returning one level of the iteration and down, rather than all the way up and down. But even if you don't care about the work, just imagine the bookkeeping you would need to implement to support the return of the permutation generator all the way up and then calling it again and it needs to find the right place in the permutation. In other words, the recursive structure of the permutation generator keeps a lot of state beautifully captured in the call stack. If you wanted to return from the recursion and somehow maintain that state, meaning what iteration, sorry, what permutation goes next, you would need to do quite a bit of bookkeeping. Now just think about how to take this recursive structure and adding the bookkeeping to it so that it can return from the point of print all the way up, return the permutation, then it would be called again from the iterator and it goes down to the recursion to the right spot. It would be difficult. And this may be a silly example because who cares about permutation? Turns out that realistic examples where lazy iterators are important like traversing trees, essentially all of the same kind. So if you understand why turning this into the regular iterator would be difficult, you will understand a lot about why lazy iterators are so powerful in practice. So if you don't understand, try to turn this into something that works with the regular iterators, which Alamdas, which are called and they return in each iteration. And you will see how much bookkeeping you would need to add, okay? It looks like somebody is ready to ask a question. Right, so you would need to somehow store the call stack and restore it, but in typical programming languages, you don't have the access to the call stack, right? You cannot say take a snapshot of the call stack, restore it and put the PC right there, right? You cannot do it. You would need to create a data structure that essentially keeps track of what's in the call stack. And I'm not saying you cannot do it, just harder, okay? So how are we going to solve this problem when the usual iterator architecture, so to speak, doesn't work? So what would make it possible to sort of preserve that structure of the recursion and then return back to it? What if we were running the permutation generator as a separate thread that we could somehow suspend at the right place and it sends us the value back as a message and then we say, good, thank you. And when we need it again, we'll let you do another recursive step and it will give us the next value, would that work? Yes, no? Essentially, we would have, think of it as two threads, each of them with their own stacks and they communicate through something like message pass. See a problem with that? The real problem is that we have the recursive call which is deep in the recursion. You wanna preserve that stack. So, well, let's just do a sort of suspension of the execution. So we'll do it exactly that. So this one, the one we discussed wouldn't work. We cannot just say return here because that only returns to the recursion level above. So it doesn't quite return all the way to the loop. And this is our discussion here. So what do we do? We essentially want to somehow jump from the recursive generator to the via loop and back, preserving the stack. So let's think of it as a, here we have the recursion of the generator. We would like to from this lowest level go back to the loop and then back. This is our loop body, okay? So this is what we wanna do. We create two execution contexts. You can really think of them as threads, but we'll call them coroutines. The reason are that they are cooperating threads. They are not threads like in UNIX where both of them run, they can interleave arbitrarily. They will really decide when they are transferring control between each other. They really have one thread of control which means only one of them runs. And these two tasks, these two threads decide when the control is transferred. So how do we do it? We'll have three constructs. We'll start a new task or coroutine, we'll call it with this construct. We'll give it a lambda, that will be the body of the coroutine. Then some sort of a handle to the coroutine is created in the process. We'll pass it into the resume construct which will start it or return to it. We can also give it some arguments. And then the coroutine itself will return to the other task, the creator with the yield. The yield also can return a value. So let's see what happens here. So here we have the create coroutine. Here is the body of the coroutine all the way here. What will happen at this point? Where does the control of the execution jump after this? Well, so are we returning here? We can think of the coroutine as a task. So when we create it here, it's actually not running yet. We sort of just create one that's not active. It's ready to run, but it's not running yet. So actually when you do this, at this point we are going to that point here. And this yield is going to go in here. So this will print one. The second resume is going to go where the yield left off and you print two. Then you go back here into the next resume. And you print three. And when this one resumes, what happens? We are resuming into it for the first time. What do you think will happen? Well, we could decide that we'll try to print three, but it would be difficult to implement because you sort of have a special case. Right now you are clearly at the end of the coroutine and you need to somehow discover you at the end and somehow remember that you are still here, but not really. What we'll do, this resume wants to go to a coroutine that has ended and so an error would be thrown here. One, two, three is printed and now there is an error because we are resuming into a finished coroutine. How about this? Now these yields themselves can return a value back to the creator. So you can think of this as the coroutine and this is the master. So this is the master and this is the slave. And again we print one, two, three and here again we'll get an error. Actually the master would be, oops, the entire thing here. Okay. So you think if the resume will execute what's left which is nothing will give you back nothing? Well, so the question is clearly the way I designed this protocol here is broken because the master has no way to ask is the coroutine still alive? So we could support a call which essentially queries the CEO and says can I still resume into it or is it finished? Okay. Another way which is what we'll use in our implementation we'll just ask the coroutine to yield null at the end and then we can use that to decide, oh I see, it yielded null which means it's finished. But again another way would be to just have another call that looks at the CEO, the handler of the coroutine and asks is it still active or not? Well then if you yield null somewhere in the middle you then you are breaking the agreement and you effectively are telling your master that okay I have finished even though you are not at the end and that might be the right answer. You know logically the coroutine has finished the return null to the master and it's saying I have done my work even though the control is not all the way at the end. Well if you want to use it for some other stuff then you need to use some other value to decide. But again there are two different ways how you can let the master know. Either you implement in the language of asking is the coroutine done or a special value is returned that signifies the end. Okay so now let's use coroutines to build this permutation generator that is actually an iterator that will get us the permutations one at a time rather than all two to the n at once. So the first thing we do and that's beautiful that we'll just replace the print with a yield. And that's a useful trick. Whenever you need to know how to write lazy iterators based on coroutines first write it the way you would do it with print. Imagine you just wanna print the results and then replace the print with a yield. It's not all but this is the first step. Well the data structure A is essentially a list and what I'm saying here see when I call it from the main A would be n elements of the list, okay. And if you notice here what do I call back into recursion passing the entire list down into the recursion. So as I recurse I still pass the whole thing but I'm just shrinking the value of the second argument. So I'm passing the whole array but I'm passing also information about really permute only n minus one elements of the array. And therefore you can at the bottom of the recursion print the whole thing. So in other words we are not shrinking the arrays we are going down to the recursion. So the first change is nice. Just turn the print into a yield. What else do we need to do? And now the trickier part comes. So in order to make it into an iterator what do we need to do? We need to return a lambda that can be called by the loop in every iteration, right? Okay, that's clear that's the protocol the for loop calls a lambda. What will that lambda do? We do have time actually. So let's make this the peer exercise. Why don't you grab a piece of paper? And work out what would be that iteration factor. Remember iteration factor is the function called by four at the very beginning of the execution that function returns that actual lambda, right? Let's go here for example. So here is the iteration factory. Here is what we call we call iter and it gives us this lambda here, okay? And now we want to build something like this but for use with coroutines. And remember what coroutines do? You create a coroutine with the call to coroutine and a lambda the body. Now you can do resume into CO, okay? And the coroutine can then yield back with some value and the yield goes to the place where you resumed before. So you resume into a coroutine and then you yield back a value. So by the way, these coroutines are a construct that in Python you call generators. Some of you know and with these generators you can do beautiful things. Essentially write libraries that are easily reusable by others. Essentially they are language building constructs. So it's good to know how to program with them. And permutation is just a nice simple elegant example. I'll show you a few more later. Can implement regexes, regular expressions with them because these generators support backtracking in an elegant way. Okay, so let's go through it step by step. So we want to write a construct like this. We wanna say 4x in permutation iterator of some list A. So oops, no. So the permutation iterator is not the iterator itself. It's a function that when you call it it returns the iterator. So it's an iterator factory. And here it is, right? So this is an iterator factory. And this function here is the actual iterator that we'll call in every iteration of the loop. So what will be the one crucial thing that we'll do inside that iterator? So there are a few things we need to do, right? So we need to somewhere create a coroutine. Create a coroutine. Then we need to return the lambda, the iterator. And then what is the third step that we need to place somewhere? We need to do a resume, right? We have essentially three statements. Create, yield, and resume. The yield is in the coroutine. This is yield is sort of like a return statement but you are not returning at the end of the procedure. You are sort of keeping the context, right? When you yield you stay right there and the execution then resumes at that point. Okay, so where do we do the resume? How many times do we resume? Once at the beginning of the loop, in each iteration depends, in each iteration, right? The yield and the resume are matched, right? Yield is done by the coroutine which is sort of slave to the loop and the resume is done by the loop. How does the loop does resume? It does it by calling the iterator, right? So somewhere here, inside here, we need to do a resume, okay? So now we know where the resume goes, we know where the yield goes. Where do we create the coroutine? So somewhere here, you mean? Okay, so let's create the, oops, coroutine here. Coroutine and the body will be perm gen, well, if I do this, what will happen? Now, functional programming, right? So if I do that, what will happen? Thing about in this language, when are expressions evaluated? What is perm gen? Perm gen is a function, right? It's a pointer to function, it's a closure. If I put parentheses next to it, what will happen? A function call will happen, right? You jump to perm gen right here, okay? Is that what you want, right? You want to pass into that call to coroutine just a handle to that perm gen function. You don't wanna run it actually, okay? So this here is crucial, we cannot do that. We just pass in perm gen, good? Well, let's see what will happen. So let's just start with that. We'll just pass into the create coroutine this and maybe we'll learn from our mistakes. So, okay, would this work? So when this is called for the first time, and it's called only once, right? Before the loop, we create the coroutine, that's fine. We have just created a coroutine. The coroutine will be waiting here at the beginning of perm gen, right? You can think of it as hasn't started but the program counter is right here. And CO is a handle to this coroutine which is ready to run but it hasn't executed anything. And then this factory returns this lambda to the for loop. The for loop then calls the lambda when? At the beginning of the first iteration, right? And what happens then? We call resume, which will go here and recurs and recurs and recurs and recurs until it hits yield. The yield returns the first permutation of the list. Where will our control go? It will go right here, okay? And it will be the return value to the iterator as we want. So, so far so good, is this it? Well, actually resume does take an argument. You can pass values both from yield to the master and from the master to resume, okay? So, at the beginning it is the argument of the perm gen and then later it is the value that the yield evaluates to. So, I'm not sure we are going to ask you to implement the passing of arguments, I mean, from the master to the slave through yield but the values could go from yield to the master as well as master to the yield and it sort of looks like the return value of yield. You get this, okay. So, the answer is that it won't work because when you call it for the first time you get the second value. Oh, I see. So, not quite, when you call coroutine it does nothing but creates a coroutine. The coroutine after you created, sorry you can think of it as it's waiting to run but the program counter of that coroutine is right there at the beginning. So, this call, the coroutine call actually doesn't run the coroutine. It doesn't go to the first yield. So, that way we are good. Okay, so let's look at what we have here. Okay, so almost, we got it almost right. And in fact, the limitations that we have on argument passing suggest to do what you just said that will pass the argument the way you are saying. So, what is the one thing that we have fixed in this version that our brainstorming didn't do right? See this null here, right? So, what does the null do? Right, when the loop calls the iterator again perm gen is done meaning it has returned, right? It actually doesn't return an undefined value but it returns this null and that terminates the loop. But we could also put the null at the end of the perm gen and then we could avoid this lambda there, right? Yes, no? So, the last return statement, right? Essentially the implicit hidden return statement. So, if that's how we define it that, yes indeed the null would be redundant and we don't need to put it there. But this is a nice example of showing if you don't quite know what that procedure does is sort of nice safety to put at the end. But you are right, if that procedure returns null then then we are safe. So, this is a little bit tricky to wrap your head around but it's a powerful construct. So, let me show you now what we can do with it. So, imagine you want to iterate, you want to iterate over multiple collections simultaneously and make this collection something fun, like binary search trees, right? What are binary search trees? Trees in which the keys in the left subtree are less than the key here and the keys in the right subtree are greater than the keys in your node, right? Should be familiar with binary search trees. Allow you to find an element in the tree in log n time assuming the trees are balanced. And if I just ask you to find a node in the tree you would know how. If I ask you to iterate over the tree and print every node, every key, this is how you would do it, right? Simple recursive pre-order function. Okay? And there is a reason why I'm showing you this because there is a print statement here and I told you that if you know how to traverse a data structure and print it, you know how to turn it into an iterator with the coroutine. You just essentially replace print with a yield, all right? So how do we traverse the two trees at once and why do we wanna do it? Well, perhaps we wanna merge the two values from the two trees into one tree or just merge them into one list, okay? If you are forced to write the code like you see here with the typical recursion, you would find it difficult to recurse over the two trees simultaneously, right? Because you would need a call stack for each. But with coroutines, it's easy. So here is how you will turn this printing traversal into an iterator. You just replace the print with a yield, like before, and we'll wrap it into a coroutine and an iterator just like before. So here is our iterator here. You resume into that coroutine in each iteration of the loop. And what it does goes to the next yield, returns, and so on. So think of the print yield translation as very useful. Okay, so now we are ready to do the merge. We create one iterator, another iterator. We get the first value and the first value from the second tree, right? So far so good. And now it looks like a merge sort. While you have this one value or the other value, while one of them is non-now, okay? You check which of them is now. And if both of them are non-now, you compare them and accordingly you say, well, I'm printing one or printing the other. And if you are taking the value from the first iterator, you also need to advance the iterator. Or when you print this one, you advance the other iterator. So this is really nice. You know how to do it with arrays in merge sort. You sort of pick one element from one array or the other. Now you can do it over arbitrary data structure for which you can define an iterator. You don't even need to know whether it's a tree or not a tree, okay? So your little homework is to figure out how to actually turn this merge into an iterator. I showed you how to take this recursive traversal of a tree into an iterator. Once you have, here it is, once you have that, you can turn it into a merge, which picks one element from each tree at a time. But this merge itself could become an iterator. And in fact, you can take two trees, merge them on demand and pick one elements at a time. And this routine over here will sort of advance step by steps as needed. And so in that sense, these coroutines allow you to build really composable iterators, composable data structures. And you haven't seen all the cool things you can do with it, but we'll look at it next time. Okay, thank you.