 The last time I had dinner with our first keynote speaker, it was a dark and stormy evening in Columbus, Ohio, where myself and Sam and our speaker saw a procession of police vehicles doing handbrake turns into a parking lot outside our chosen restaurant. We never did find out what happened that night, but I digress. What that evening had in common with most other times I've met Brandon is hearing a wonderful collection of stories and amazing insights about Python. Unplug that one. Amazing insights about Python. And when it came time to choosing our first keynote speaker, it was an obvious choice for all of us on the team, and we're really glad he agreed to come along here. Here to share the joy of Python with you, please make him welcome Brandon Rhodes. The screen's working now, right? This is known as an ASCII art animation. It is made of nothing, but characters that you can print to the screen, colors that you can display on it. Every block that you see is exactly the size of a character in this terminal window. This is a terminal you're looking at that I've used to create an animation. And I'm here to talk about that, to talk about animating with ASCII, or an analysis of the code behind my Python 2017 talk at the inaugural North Bay Python. I'm Brandon Rhodes. By the way, the Wi-Fi password I figured out after he said it is, I think, Python 2017? I was sitting there thinking what version of Python is 2071? I had no idea. So my Python 2017 talk on the new features that have been added to the Python dictionary used ASCII animation. To give you a small taste of that, I was in a terminal screen, and I would run it and be able to show how when you insert keys into a Python dictionary, the first few are probably going to fit just fine according to the last few bits in their bit pattern. But then, if you add one that tries to use the same bit pattern, it is going to collide with the key that's already there and have to be put in a backup location. And I thought, I mean, we call them collisions. Shouldn't they look like collisions? And so that was the idea behind using animation in my 2017 dictionary talk. I wanted things to visibly collide. So people then asked me, how did you do it? How did you make these seamless animations in ASCII? And so I thought that they deserved an answer. And that's the purpose of this talk. And the answer is I did it badly. And so I'd like to explain that. First, a little bit about my philosophies as a speaker. Some guardrails that I tend to put around my talks. First, I always have a per-timer slide that tells me, based on how many seconds are left till I need to finish the presentation, divided by the number of slides that are left. I always have a timer that tells me, well, you know, you can only spend 15 seconds on this slide before you're eating into the time of the remaining slides. Because all I have here is a terminal screen to help me with this. I have an assistant that will be called in whenever I hit that time limit and that will begin to destroy the current slide so that I have to stop speaking about it. So there it goes. So that's how I'm going to try to rein myself in and not turn each slide into a lecture. A second thing I do in designing talks is I try to do something with the tools I'm using to constrain my design space. For example, in most of my neural presentations, going back to my very early talks at PyCon, I write them in restructured text about the only tools I have available are to make a paragraph, to make a bullet list or to make a block of code. And I have to somehow come up with a way to communicate all my ideas just from turning those elements into HTML and applying some custom CSS. So doing an ASCII animation presentation kind of fit the same mold. It gave me a limited set of tools, a Python script, a terminal window in the Ubuntu monofont that prevented me from sitting and playing with a font all day or a kerning two characters to get them chest right. By taking that option away, I don't spend all my time fiddling with pixel by pixel detail. I guess I fiddle with character by character detail instead, which is a lot faster. There's a lot fewer characters than there are pixels. Third, I try to go with very brief slides. This is made famous by Lawrence Lessig a decade or two ago. The idea is that short slides are less distracting for the audience. Like right now. You're listening to me rather than reading ahead to my next few bullet points, aren't you? Because you don't see them until I advance. Short slides keep you on track. If each slide is essentially just the sentence you've got to stay, it will be easy to stick with it. They also decrease the load on your memory as a presenter. Easier for the audience and easier for you, the speaker. So I always try to go with very short slides if I discover during practice of my presentation that a slide has two ideas inside, that the slide is making me say one thing, and then remember and say another, I split it into two. This presentation has 129 slides, for example. Each of which are a simple idea that I say and move on past. No big lectures that I have to give. Fourth, and finally, use a big font. How big is this font? It is the biggest terminal font that still fits the code on slide 86. Literally. So lay out your talk and then just use small code samples and increase the font size until the largest slide barely fits the screen. I'm often just stunned by the fonts that people think I can see from the back row. So those are my basic guardrails. A per-slide timer, so I keep moving, constrain the design space by using very limited tools that I have to work within, brief slides that each have one idea unless it's a recap slide with four, and use the biggest font I possibly can. So all of this worked very, very well with this idea of having the presentation in a terminal window. So I went to PyCon 2017 and I gave the talk. I wrote my own ASCII animation library because, frankly, I couldn't understand the others. On PyPy, I did find a few ASCII animation libraries, and I looked at how they worked, and I'm like, I can't understand how to make my animation, how to write it in this framework. I'll save time by writing my own. I did get the talk done. So there's two kinds of challenges that I had to dig into in order to get it done. Technical and architectural, and I'm going to tackle them in that order, which is also the order, the ones I did pretty well and the ones I did less well. So it'll be a good order for the story. The technical ones we can breeze through. The technical challenges were as follows. I wanted to have, in the middle of this animation, I wanted to be listening for keystrokes and have forward slide, backward slide, and repeat the current slide, if I wanted to show the animation again. All there at my fingertips is actions that I could run. But in this loop that's running the animation and printing stuff to the screen, of course, if you say keystroke is, this standard in read one character, what happens? The animation stops and waits for you to hit a key. And the animation would freeze if I asked for a character, if I went and checked to see if a character had been typed. All right, I knew my way around this. I went and consulted the documentation, and I used the select call from the identically named select module in the standard library to check whether there were any characters waiting before potentially blocking myself trying to read them. So that got past the problem of sys.standardin.read wanting to freeze my animation. I could go read the character only if I was sure I had typed one. But my program then, it was now running, the animation wasn't frozen anymore, but it couldn't see my keystrokes until I pressed enter. Why was it ignoring my keystrokes until I hit enter? Well, this gets into a little bit about how your macOS or Linux machine thinks about the terminal. You might think that your process is connected up to a raw device that lets you write characters to the screen and listen for characters from the keyboard. But it's a lie. In between your process, your program and the terminal is something called a term iOS driver. The term iOS driver is the source of many lies and deceptions. It's designed for the days when terminals were very simple devices, teletypes sitting in bell telephone laboratories. The kind of settings are designed to help programs operate on those old terminals. The term iOS settings that we're about to look at are not controlled, thank goodness, by writing special sequences. You don't write some bytes and have the term iOS driver wake up and think that those are settings, because what if you needed to write those characters to the terminal? Term iOS gets out of the way in that respect by having you use an out of band, not in the right call, system call, to set its attributes. You can go learn about all of those attributes in all of those settings by running man term iOS, or you can do it from the command line. Run STTY, which is short for set TTY settings, dash A for all, and you will see all of the settings that a modern Mac OS Darwin or Linux machine has available, all of which can edit the data going out to the terminal or the keystrokes coming back before you ever get a chance to see them. Several of these settings were fighting me as I tried to write my animation library. Here are the ones I had to adjust. First, why have a driver between you and the terminal? Well, this brings up the topic of the first setting that I want to talk about. In the middle of that big man page is something called O, meaning a prefix, meaning this is an output setting. New line carriage return. This sets up the rule that when you output a new line, the terminal actually gets sent to characters. Carriage return is a character, it's a single byte character that when you send it to the terminal, moves the cursor from wherever it is back over to the left margin. It is a favorite of people writing ASCII progress bars. All you have to do is carriage return, write your progress bar, carriage return, write it again, and you get what looks like a single line that doesn't scroll everything off the screen that changes each time you write it. It's friend, carriage returns friend, is called line feed. It, strictly speaking, just moves the cursor straight down. It stays in the same column but moves from one row to the next one. You can see this, in fact, I'll bet many of you have seen this before, as follows. Now, for simplicity, Unix files only put a line feed at the end of each line. So when you run PS, a sample program, just to use it as an example here, something interesting happens. It looks like each line ends with a carriage return moving back to the left margin and a line feed moving down, but it's a lie. PS is only putting, it's a Unix program, it's only outputting line feeds at the end of each line. And then, oh, hello. And then, so I guess the challenge is, can I finish before it's finished destroying the slide? So PS is simply putting a bare new line character at the end of each line, and the terminal driver is the one adding in those carriage returns. We can see this as follows. Unstty dash for off, O-N-L-C-R, and then run PS again. Have any of you ever been in a terminal window where you ran a normal command like LS and the output suddenly stair stepped strangely down the screen? The reason is that this is the truth. In truth, PS only asks to move to the next line, and it relies on the default terminal settings to get it back to the left margin. So then that's probably the most famous setting that people run into the most often, but some others are, further down on the man page, there's an echo setting that is on by default. Everything you type is echoed, type the letter R. It'll just appear on the screen, whether the program you're using wants it to or not. And termios like all good tools includes a built-in text editor, right? All tools need built-in text editors. That's turned on by default. So that text editor is called canonical mode controlled by the eye cannon setting. I will do a short demo. Normally if you type cat and start typing a line of text, cat has not yet received any input at this point. Why? Because the eye cannon editor is reading my keystrokes. Echo is on, so I'm getting them sent back to me, and it's not until you hit enter that the termios line editor concludes that you're done and all at once sends those characters to cat, which then presents them back. If I STTY, turn off canonical mode and run cat again, you will now see that everything, my backspace characters, everything else, are going straight through to cat because the text editor is off, the illusion is gone, and cat is having to deal with my raw keystrokes as I type them. The only crazier thing I could do would be to turn off echo. Cat, enter. Hello, world. I hit backspace, enter. Control C, PS, LS, PWD. Bash has never actually been echoing those characters back to you. It just leaves the termios driver in the default mode where it echoes the characters for it. So, these settings, the fact that the line editor is on by default and the characters are echoed by default, is great for, you know, programs like, oh, and I want to show you a normal program now, I'm going to run a special command called reset. Reset is utter magic. It sets echo back to the default. It sets all of the settings of not only the terminal, but the termios driver back to the default gets you back to normal. So, I can show you that for very simple programs like the desktop calculator, six, six, add them in print. It's an RPN calculator built into Unix. It doesn't have to know about line editing or backspaces or anything. It just leaves the terminal in its default state, and a line editor is automatically sitting in front of it. So, it made small programs very easy to write that needed to work at the terminal. Reset, command I just showed you, might for most of you really be the most useful take away from this entire... From this entire keynote. And so, I will highlight what I believe might be the most useful sentence or pair of sentences in the entire Unix documentation, not that they ever tell you where to go find it. So, I will quote from the man page to reset. This is useful. After a program dies, leaving a terminal in an abnormal state, note you may have to type line feed, reset line feed. The line feed character is normally control J to get the terminal to work as carriage return may no longer work in the abnormal state. You're probably used to being able to hit enter to end the line. That's because of a terminal setting that can get messed up. Control J is really the thing that will get you to the next line. So, I needed to turn those off, and this is great fun. You get to, like, go into 80s retro mode because these settings are each a bit. Two and eight are powers of two that define two bits that we need to turn on or off in the big word of bits that is the termios settings. So, we have to get the output flags by using a standard library module called termios, and then we can get those flags and them using the vertical bar with the echo bit, and you can see that that fourth bit gets turned on to turn it off only time you ever get to use the squiggle in Python. Another use for this, the little squiggle, the tilde operator, gets a word and flips all of the ones in zeros. So, we now have a zero where we want to turn a bit off. We use and to get the bit turned off in the settings. It works great, it's fun, and it's like this 80s moment in the middle of your programming day. So, those are the settings and the things I had to be careful of were the termios driver, things like echo, things like canonical mode. What then did I have to learn about the state of the terminal, which is also a stateful appliance which changes as you use it. To talk to the terminal and to change its settings, you have to use ANSI escape codes. These are an in-band signal. This is in the middle of sending characters. You throw in some special character sequences that don't get printed to the screen. They instead change the terminal's state. This fact that these are in-band signals, that they are in the same stream of characters you're outputting, led to an interesting hobby people used to have. They would share animations with their friends as plain text files. Because a text file can have printable characters in it, it can also have these special escape codes. Let's go back to our little standard 80 by 24 window. Apologize for the small font, but this animation happens to be that size. My father worked at Bell Labs, and back in the early 80s, right at this time of year, what is this, 2nd of December, I think? Right at this time of year, people would start sending back and forth to each other a little file called train.txt. I see someone who's very excited. Are you ready? So imagine you're at Bell Labs. It's 1982. Someone sends you this text file, and you say cat train.txt. Aw, wasn't that great? What went wrong? Well, that stream of text characters has no timing information inside of it. There is not, on most terminals, a way to say wait one second, and then continue processing. Unfortunately, on a modern terminal, we don't get to see the animation unless we write a program that prints out a file at 1200 BOD. It's just a little loop that pauses between each byte. Here is what it would have looked like on a 1200 BOD terminal on someone's desk at Bell Labs. The flashes are where it's ringing the terminal bell to make a ding-ding sound. Here, it's going to ding again. Ding-ding! Now, I'm going to run less on this file to let us see the truth behind it. And less is not... Less is a smart program. It is not going to let the animation start moving the cursor all over the screen. Less is going to use, I think, reverse video to show me where special escape codes are. And so you can see that at the top is the email headers. Oh, I'm sorry, this is 87. That this was last sent from one person to another. And you can see here all of these escape codes, all of these special characters that aren't the 9695 printable ASCII characters but are drawn from that first 32. Remember always that man ASCII, if you spell it correctly, will get up a table that lets you remember how many 32, the 32 first character codes are all special ones that mean something to the terminal. And this is a text file that because all of these instructions to the terminal are in-band are just bytes in your data stream, you can store them to the file and watch them decades later. If I may, for one moment, tout the Unix system, how many other animations from 1987 can you still play? The design they did has very, very great longevity. So, train.txt, these are in-band signals that you just write like any other normal character, but they do things. They move the cursor around and things like that. So I could have used a big terminal library to introspect the kind of terminal I had and write the codes for me. But I don't know, I'm not that kind of guy. I just did it this way. I just said, look, there's like five things I need to do is all. I need to know how to hide the cursor, show the cursor, why? Because notice how annoying it is, we're gonna watch train again. Watch, because the train animation is constantly sending the cursor all over the place to draw the little train engine and to make the lights on the tree blink, there's all these little ghostly cursors. Do you see there's this little, all these little cursors like following the train down the track? That's because this was not written by someone who knew to turn those cursors off before displaying it. At least when I was preparing for my PyCon talk, I thought the cursor was really distracting, having it flicker everywhere. So I used hide cursor, hide cursor to hide it, show cursor turns it back on when the program exits. The only other things I needed were go to origin, because I just go to the upper left corner and just print as many characters as there are characters on the screen, and it just fills up each row with the slide I want to display. And then I need to set the foreground and background color to not part of the slide to be a different color. I did all that inside of a try finally, so that if my program crashed, I think it did once as I was editing it, it would set, I wouldn't have to type reset if I set the echo and eye cannon and show cursor back to normal, and always set your colors back. It is no fun to have it die when the color is white on white, and then you can't see the trace back. The next technical thing that I had to tackle was the fact that the talk was about the difference in dictionaries between different versions of Python. I understand this animation program was actually creating dictionaries live and drawing them on the screen, so from one slide to the next, I might need to change versions of Python so that it would change features and behavior. So I used a Unix superpower called exec. It is a call that replaces a process, the one making the call, but it is a new one without leaving a dangling parent process hanging around, waiting. Here is my favorite shell, the Z shell, I run PS, okay, my shell is currently process 7807. Let us, if I ran binbash, if I just said binbash, then 7807 would now be the ZSH process, a parent process waiting on bash to complete, but I know I am not going to use ZSH again in this session. So let us exec binbash, I am now inside of bash, and it has become process 7807. You are giving your slot in the process table to another process without leaving a parent dangling up behind you. This is very important if during a slide deck you might restart Python 128 times, as I am doing here. So there are several versions of it that are a bit of a confusing thing. I found execvp was one that worked pretty well for me in the OS module. All I had to do was find what version of Python I wanted to run for the next slide I was advancing to and then invoke it. The, oh, I hit repeat instead of forward. Here we are, we move forward. This meant that each of my slides could run under a different version of Python, and it also meant I got rid of the need for auto-reload. I didn't need to have, I didn't need to stop and restart every time I edited my slide deck program because I was automatically restarting every time I moved to another slide. It was really convenient. It even wound up helping me with dictionary stability. One problem is as I moved into the more modern versions of Python, the dictionaries looked different every time I ran because of the randomization they do and it's very hard to build narratives around dictionaries when you don't know when the keys will finally collide. At first, this was very inconvenient. I had to, because that's something that can only be set at start-up, you can't adjust it later, I would just have to exit with an error if I forgot to set that environment variable when I ran my slide deck. Every day when I'd sit down to work on it, I'd have to run it twice to remember that. Once I'd realized I could use exec, I just put an if statement at the top that says, well, if the environment variable I needed to be started with wasn't there when I started, let's just re-exec myself exact same program, exact same arguments, but throw in the environment variable so that Brandon doesn't have to remember to. It was really helpful. So those were the technical challenges and though several of them snagged me for a few minutes as I had to relearn how terminals worked, I got through them fairly well. We will now talk about my architectural issues. How can I store the screen? Now, I started off pretty well. This part survived. How can I store the screen to allow random access modification? Since if I made a big long string that holds all of the rows of characters, it's difficult to edit. So I decided to make what I called the canvas I was drawing on, which is a list of characters that starts out as spaces, one for every position on the screen, starting in the upper left and going in the same order I'll need to print them in, down all the rows to the bottom, and then two corresponding lists of foreground and background colors. If I want to write to this because lists are mutable, I can simply use pythons from the bottom three lines here, really beautiful range assignment operator, and another part of the program wants to scroll some text onto this canvas at positions X and Y. All I need to do is I can just say we'll set characters I through J to the characters from that text, and then go in and set the foreground and background colors as well. When I was ready to print it to the screen, easy, and this part again, survived and worked just fine. Essentially just for each character yield the character, and then the color will empty string dot join this all together into a single bundle of text that it can write so that the frames appear quickly and as solid piece as possible. But I also, whenever the color changes between one character and the next, need to yield the special code to the terminal that will change so that if I've gone through some characters that are black, they'll change and send that signal so the next few will be red or whatever. But I only, those who remember those escape codes you saw a minute ago are kind of big, so I only output them if I can't just reuse the color of the previous character. That all worked great. The problem came when I had to tackle the question how do I write animations using scroll? I first thought of it like this. Well, animations unfold through time. An effect might last for a second or two or three as I animate a Python dictionary. And those seconds are made up of individual frames. I think I wound up going with 60 per second because that's a typical refresh rate for a monitor like this. Smooth as possible, 60 per second. How can I go ahead and plan out which effects will be happening during which frames? Well, I like data structures, and I thought I'll just build a data structure. Before hitting go, I will study the current slide and I will go through and I will set up for each frame the animations that it should call, the things that need to appear on the slide for it to appear. Then I can just do a loop starting at the first one, call the first list of functions on a canvas, second list of functions, and for each of those, print a slide to the screen. Here's a sample animation. So we have an example to talk about. Beautiful is better than ugly. Each one fading in over exactly one second. I'll run it again. So that's a sample animation that I can now use to kind of show you how this worked as I wrote a routine called fade in. You give it the script and it's going to add itself to a start time, a duration, and the X and Y location in the text that you want it to display. All it's got to do is go add some kind of function or callable to the correct frames in that list of slides to get itself scheduled so that as the animation proceeds those will appear. This was my first try at getting animation working. So we need to write fade in. We need to build some kind of callable that it can stick into all of these pre-scheduled frames. Well, where are I going to get one of those from? Python has essentially two best of class ways of doing this. One is a bound method. You create a class and you give it a knit method. It's going to add the animation about the animation it will be doing. And it then has to store away all of the relevant information that will be needed during the animation itself. Self dot duration equals duration and so forth. And then it can get its method called self dot draw and go add it to the correct frames in the script. What is self dot draw? It is the actual color of scroll to the canvas at self dot x and y having consulted the current time t that's also passed in to figure out whether we're now at the point where we should be all the way black or still partially gray. And that would work. However, there's an option I always enjoy better, which is a closure. Self goes away. All of those assignment statements go away and you use the magic this was added to Python in the 2000s. You use the magic of the fact that if you save away an inner function, like the draw here that's inside of fade in, it for free will remember what duration x, y, and string were back when it was defined. So here I'm appending a plain draw function, that inner function to the script where I want it to be called and later without meeting self and instance attribute references, it will be able to get to the information it needs. There are two disadvantages that I will go ahead and say despite my assistance. Two, having a class. One, on a class instance, if you want to go back later and look at what duration and x and y were from outside, you can do it. With a closure, there's no easy way to having been returned that draw function to go ask it what it thinks x and y are. You also can't modify it. X, y, and string are just fixed here. They can never be edited later. In this application, I was never planning on going back and introspecting this fade animation or trying to change its x and y. In applications where you need to, a class instance is really the best thing. Closures are great when you just want an inner function with a memory that isn't going to forget things, but that you don't need to go introspect or modify later. That worked pretty well. I got a first few words to fade in and appear on the screen. They looked kind of like professional slides. This was going pretty well. Then I ran into a problem as soon as I reached the next level of complexity. What if the text isn't fixed, like the word beautiful? What if it is generated by another callable, like something that knows how to draw a big Python dictionary? If the slide, if the material I want to fade in is produced by another draw, another animation routine, well, I'm kind of stuck. My draw routine is called with T and Canvas. I know I'm going to have to call that other animation with T and Canvas, but that other animation is just going to go draw to the canvas without asking me. I won't know which information on the canvas was drawn by it and should be affected by my fade and what information might already have been there from other components drawing to the page. I thought for a moment, what if I just monkey patch scrawl? And then I thought, I thought, no. Monkey patching is software bankruptcy. Monkey patching means that things have gotten as bad as they can possibly get. So, I wasn't ready to give up. I thought through, what is my problem? My problem is that animations aren't composable. These little things I'm writing that draw can't be stacked or layered. I looked at the code again and made a crucial observation. Canvas that I'm passing to every single animation is never actually touched by an intermediate effect like fade in. And so I thought, what if I switch up passing a noun, the canvas and instead pass the verb. Because a bottom level draw routine, it never goes in and indexes the canvas directly. It always goes through scrawl because that's more convenient. So why does it need to know about the canvas object? Why am I even passing that? What if I pivoted to making these animation routines simpler where they know a bit less about the way the framework is structured? What if I just give them the scrawl routine that they are supposed to use and don't let them touch the canvas yet? That was the big win that let me finish this out and get a talk ready for PyCon 2017 back in May. Because this change, one little change from a noun to a verb, gave me composability. When I am called, time to draw slide 50. And so draw is called with 50 comma and presumably the scrawl routine that we'll really write to the canvas. I call the next animation down with a different scrawl routine that applies a fade to each piece of text that's displayed. All of a sudden I'm in control of what that subordinate routine does and can make its output be I can adjust its output in any way that I want. Having a function inside of a function inside of a function seemed a little bit much but it was time to finish the slides up and attend the conference. So that's the form the library was in when I presented at PyCon 2017 which is why I haven't shown it to anyone. So many wrapper functions functions inside of functions. So after PyCon and in preparation for this talk I wanted to think about that disaster and what had led to it. So let's review. Fade in, this animation layered on top of another one needs to have an interfunction that itself needs to have an interfunction and the draw dictionary and it has a subordinate one draw item that draws one item of the dictionary so if they're going to be intercepting and using a lower level drawing tool also have to have their own version of scroll. So let's, to see why trace where the data goes. Do you ever do this with your program, sit down and draw a picture and trace where the information goes? The order here is that first we call fade in and it gets its.draw and goes and puts it into the script. Then as we run through the animation every frame is going to call draw with a different value t it is going to call draw dict it is going to call draw item which thinks it's calling the real scroll function scroll function but is really calling the inner scroll function of draw dictionary that calls what it thinks is the real draw scroll real scroll function that writes on the real canvas but it's not, it's the one from fade in that then really gets to draw on the canvas. Now let's look at the data, so this worked let me say this worked but I wasn't happy so let's look at that picture and think about the data that's passing as draw calls dictionary draw calls dictionary calls draw dictionary calls draw item like nothing much is changing all we're doing is passing the current time all the way down without even modifying it the current frame number the interesting thing happens on the way back we start with an x y a string in some colors and that changes at each level as it's passed up it's almost like hiding inside of this horrible set of inner functions is a system for producing tuples and then applying simple filters to the values in the tuples can and this is how this presentation is written can we write an ASCII art library that's kind of a good parts version of the one that I just described here is a no scroll solution I've restructured to explicitly defer drawing to later, now that previous one when the innermost you know one called what it thought was the scroll function I was implicitly secretly deferring it I wasn't letting it draw to the canvas or some other steps first but let's make that explicit let's make it explicit that we're going to wait and not draw these things until the end so I'm going to define a frame that we're gonna you know one of these frames of the animation it is no longer a canvas it is simply going from the point of view of the drawing routines it's going to be a list of tuples that each describe a piece of text to be drawn using the parameters we're familiar with an animation is simply going to be a generator that builds and returns these lists this is composable and I will show you how with four simple calls I can run that beautiful is better than ugly animation each of them fading in over one second here we go to center a piece of text I don't have to go right on a canvas now all I have to do is yield a list of tuples and somewhere out there the Mycaller, the ASCII animation framework will go do the drawing for me so I just given the length of the text I figure out where to center it and all I have to do I'm a little infinite animation that's going to sit there and make the text display until forward to the next one all I have to do is yield up for each frame that little tuple over and over and over again simple, easy to write no interfunctions, no callbacks ok, fade in is more complicated because it needs to edit what another animation produces so it does this it produces, given its duration the list of levels 0 through 1 that it's going to apply to fading the background color to the foreground one it then, for each frame it's given, for each list of tuples it's going to turn around and return, or yield I should say, its own list of tuples where each of the incoming tuples is edited to give it a different foreground color done if we want several animations running simultaneously we can use Zip, which remember we'll just sit there and run a bunch of generators simultaneously returning to us all of their outputs that correspond to each other what everyone thinks should be on the first frame the second frame, the third frame if I want to combine them all I have to do is loop over all of those lists making a single list of tuples to send back up to the animation engine and this is how the animation looks line 1, line 2 and line 3 are very simple primitive centerings of three pieces of text at different y values I first animation is running now I first yield from fade in which is going to fade in is going to return one seconds worth of line 1 fading from nothing indivisibility on the screen I then yield another seconds worth of animation where line 1 is now fully visible and line 2 is fading in finally for one second I fade in line 3 and end by having all three lines fully visible and here it is running switching things around and foregrounding the data and foregrounding the fact that each of these animators is returning something that other animators have the right to edit and change and filter before it gets handed back for drawing on the canvas was the key to making this a framework I was happy with that I might even open source tomorrow or something so on the challenges I faced technical challenges I would give myself an A I did an okay job back in May setting the term I O's values to get it where my animation could run that design was at best a C minus I mean functions inside of functions inside of functions but the problem is you see that there's this ugliness there but you have a talk do so sometimes you have to get something like right I got it to the point where it was barely composable we saw you know that sometimes you just have to get something to the point where it at least is sufficient and just run with it but part of the I guess reason for my giving talking about this in a keynote finally let me go back and do this right my why a C minus because this is very important to be thinking about my code wound up more complicated than the problem it was trying to solve this seems to happen to us so often but it can be so difficult and take months sometimes to see the transform that will get us out of that situation and let us reduce the complexity of the code down to something more roughly approximating you know I'm trying to put characters on the screen need it to be this hard in conclusion you can give the talk the clean architecture at PyCon Ireland three years ago in 2014 and still struggle to produce code that is shaped enough like it's data I'm Brandon Rhodes thank you very much thank you very much Brandon so good thank you okay so