 So, fantastic conference. It's really funny, because as a speaker, I quite often go to conferences and don't actually go to the conference, but always with these ones I attend because I just learn so much stuff in all the talks. I really, really appreciate it. And I've been thinking, okay, so this is the final keynote. Everyone's kinda like, the two days of really interesting stuff. So I need to give something that's really exciting, that's really motivating, that's really tremendously good. What am I gonna talk about that has all of those great attributes? And I thought, okay, obviously. So I need to talk about, well, ish, okay. So at the Pragmatic Bookshelf, we get quite a few proposals come through for projects where people have basically written some code and they say, I wanna write a book about my latest project. And on the kind of proposal review team, we call those proposals the what I did on my summer vacation proposal. So that's what I'm gonna do now. So the official title is corrupting the world, but the actual title is what I did in the first six months of retirement. So two are actually pretty much the same. So I retired back from the Pragmatic Bookshelf full time back in June. And fundamentally, it was because I had like a backlog of several thousand cool things I wanted to do. And, you know, formatting books and stuff wasn't one of them. So I sat there and I was like, dear in a headlight, suddenly faced with all this freedom. So I spent some time just spinning my wheels. But I did some cool things. And those cool things all kind of like point towards the same direction. And I hadn't realized that until I put this tour together. So let me honestly indulge myself a little bit and talk about some of the projects I've been involved in. Three. Number one is I have always wanted to try this. So I got a gig teaching at SMU. And I taught in the School of Engineering, the Bobby Lyle School, very nice looking building I taught in the basement. And I gave what I think is the first credit university level course on Elixir. I had 35-ish students, a mixture of undergrads and master students. And we went through everything. I was gonna teach in Elixir, the basics of functional programming. And I discovered that these seniors had not actually been exposed to testing. So we spent some time talking about testing. And most of them didn't understand Git. So I spent some time talking about Git as well. But apart from that, we did a whole bunch on functional programming and on concurrency, we did some gen server work. We did some work with Phoenix and Phoenix Channels. And at the end, they had a kind of open project. And I got some really cool projects back. My favorite one I think was somebody did a project. This was back in November, just before the election. And they did a project that was a, used Phoenix Channels to drive a front end. The back end was some servers that went out to Twitter and picked up responses to the Donald Trump and Hillary Clinton Twitter feeds and generated word clouds from those responses in real time. It was actually, it was fascinating to watch. It was real, I mean, I sat there and, because they submit the projects and eventually you have to run them. So I ran them. And I was just sitting there for like 20 minutes, just looking at this thing, updating on the screen, it was so cool. We had a fun time. The actual core of the class, once we got over the syntax and what is an editor, is we just basically wrote a game. And we wrote a game by starting, I had them write the hangman logic for a hangman game. So this is a module that would sit there and score moves basically. And I gave them a set of unit tests and I said, here make these pass. And then eventually we also wrote the human interface to that, the CLI. So they could actually play the game. And some of the master students I made write a computer player. So it would play the game for them. So that was cool. And then we talked about processes and gen servers and stuff. So I made them write this delegating gen server. And by delegating gen server, this is kind of like a bit of a theme of notice going through the last two days, is I really do not like putting my application code in my gen server. Because to my mind, the gen server is simply there to act as some kind of process thing. It's not really my application. So what I tend to do is to write my application logic in a separate module. And Chris was talking about this as well. You write your application logic in a separate module and then I just delegate to it from my gen servers. So I had them do that for this particular module and then for the dictionary module, it was just an inline gen servers. They could compare the two. And that all worked fine. And then we added on Phoenix and a JavaScript client. And that was all fine too. And this got us towards I guess the beginning of November and then they went and did their personal projects. But the cool thing that I pointed out to them, and which I think is actually a real big testament to the way all of this is structured, is that the very first thing they wrote, that Hangman logic at the top there, survived totally unchanged through all of these iterations. So they went from being an inline library to being a background server with state to being some part of a web application with no change whatsoever. And they could still use their human interface directly to it if they wanted to. So I thought that was kind of cool. So that was kind of like one aspect of what I did. It was totally an incremental development and it was made incremental because I get these structuring concepts built into Phoenix, OTP and Elixir. So that's number one. Number two is I wanted to play with, I don't even know what the name is for them. So let me explain where I'm coming from. A big deal at the moment in kind of like many circles is this concept of reducers. So a reducer is a function that takes a state and takes an input and generates a new state from it. And that's the function that we pass into in num.reduce whenever we wanna do things like summer collection or whatever else. It's also the signature of the main body of things like React and Elb, right? They're reducers. They're functions that respond to some environmental change by updating their state. The opposite side of a reducer is called, I have no idea what, expander or something, right? And sorry, I thought someone actually knew which would be really nice. And those are the functions that are used to generate data, like generate a stream for example. So I wanted to play with that. So I decided to have a go writing version of QuickCheck for Elixir and QuickCheck is a property-based testing framework. First came out of, sorry, Haskells? Yeah, it was Haskells and it was John Hughes, yeah. So a property-based test could look something like this. Property test A, which is an integer, B that's an integer and in this case I put a few extra parameters on, I don't really need. And I just want to assert that my integers, if I add A plus B, I get the same result as B plus A. And it's gonna generate random integers and just run that test in this case 50 times. The default's actually, I think 500, I can't remember. And you might say, okay, what's the point of that? That's a stupid thing to test. And the idea is in the same way that normal unit testing makes you think about your interfaces. I discovered to my surprise that property-based testing, it's not really about the testing. It's about thinking about the properties of the functions that you're writing, yeah? So the fact that addition is, commutative, I can't remember, is it commutative? That one, yeah, yeah. Is a property of addition. This wouldn't work for subtraction, for example, yeah? It makes you think about the properties. And so as you start writing these kind of tests, you actually start thinking more about all the various edge cases. In fact, in Quixar, I actually made it so that I kind of thought ahead a bit about some of the obvious edge cases. Like for integers minus one, zero and plus one are obvious edge cases. For strings, the empty string is an obvious edge case. All this kind of stuff. So I inject those and then I inject random values. And it's kind of fun. It's surprising it does actually find bugs in code, but like I say, it's as much a design technique as anything else. And then I had fun trying to do things like this. So A is an integer and B is an integer that has to have a value at least A. And that is a wonderful little exercise in keeping stuff until the very last minute until you execute it. I had a whole bunch of fun getting that to work. And then lastly, you can do things like this. So I'm gonna generate a structure of type person and it's gonna contain a name which is a string that contains ASCII characters and an age which is an integer between one and 125. And that all just works. So why I ended up having to do, and this was kind of like the whole point of the exercise as one of the play with this, is I wanted to experiment with how to generate streams of values. And the things that were generating those values were factories. So I ended up trying to implement something that was factories driving streams. So I decided to call it pollution. And this is the actual, this is 90% of the code in Quixar is the pollution framework. And this is the thing, it's actually useful if you ever want to use just general mock data and stuff. The API is simply you can tell it what you want using that kind of A colon int business and then it will go through and it will generate you a stream as long as you want. So yeah, I had a whole bunch of fun with that. And the key to this and what's gonna tie into what follows is this idea of stream.unfold. It's the opposite of inum.reduce in that it takes a state and a generator function and it generates a new state plus a value. And if you implement that, for example, if the function you takes a value and then returns a new state and, sorry, it returns that value as the current value of the stream and then increments the state for the next time around then what you have is an infinite stream of integers. So the first time you call it with 10, it returns 10 and 11. So 10 is the value of the stream. Then the next time you call it, it's gonna get that 11 as a value. So that's the value of the stream, et cetera, et cetera. So it's really, really simple to generate these infinite streams using the stream generators. So you can think of this unfold as being the opposite of reduce. It's kind of an expander. What does it do for us? Well, the fun thing is you can chain them together and you can change them together recursively as stream does, or you can just change them together by just calling one after the other to achieve this set of transformations where each step of the transformation generates an output. And that's where I found it was the most interesting because now I can start creating a genuinely functional solution that also has all these interesting outputs as it goes along. And all this kind of like culminated in an idea I've been kicking around a long time. So this is where I get to do my professor act, okay? If you went, how many people here actually went and did computing at college? Oh, fair number, okay. So at some point in your kind of one of the one level classes, right? I'm pretty much certain that somebody produced a diagram that looked a little bit like this. It was how do computers work? And the diagram was, well, you have an input and it goes into this box that's called a processor and then it produces an output. When I went to college, it would be punch cards go in there and the line pressure starts at that end, right? And then that go, and of course, there's also storage down here, right? Where you can actually store values and retrieve them in later runs of the program. So this was kind of like the basic idea. This is how your hardware works. This is how your processor works, your computer works. Cool. So let's tidy that up just a little bit. Speak to me. This is pretty much identical to the way a function works. Yeah, or a process works in that we have a process that takes input, it produces output. What do we call storage in this case? Thank you. We call it state. So we have these processes that produce input output and they have associated state. And when we're looking at the reducers, sorry, the expanders, right? It's exactly what we're having. We also had this tacitly when we were looking at the hangman server. It was a gen server and that's what that has as well. It has state input output. So this concept, this little three prong diagram is hardwired into a whole bunch of the stuff we do in Elixir. And often we have a state that is passed on from one of these processes to the next. And it can be passed kind of implicitly or explicitly, but that state sits around and gets used between functions. And this is where I started thinking about, okay, how can I use this in a different kind of way? First thing is, it occurs to me that you have different sets of states. So for example, if I have a gen servers, then each gen server is maintaining its own state. And each of those is independent and typically the calls are calls into functions in that gen server and those calls potentially change the state. And they're independent. That's what we like about it. That's the whole point of using gen servers. But if we go back to this idea of just having a common state that's shared, let's look at just one of those boxes. If we look inside those boxers, there is nothing to say we can't have exactly the same structure inside that box. So typically when we write our Elixir code, what we're thinking about is gen servers. And they have these, one set of gen server, sorry, one gen server has one set of state and we don't worry too much about the nesting. What I'm saying is, sometimes we think about nesting explicitly. What kind of cool thing can we do if we allow nesting to occur to kind of like ridiculous levels of depth? Can we have transformations all the way down? So all of our programming consists solely of transformations. Now that's not a stupid idea because what does a program do? The entire point of a program is input goes to output. The entire point of a program is transformation. And logically, a program is divided into sub-programs and they're into sub-programs so you should be able to do transformations all the way down. So the question I wanted to ask was, can we write non-trivial programs as nothing more than sequences of individual transformations? And the answer is yes, but it's a stupid thing to do. Individual transformations with pattern matching are turing complete. So of course I can write entire programs using nothing but those, but it's not the best way to structure programs. But while I was playing with this and convincing myself it was a stupid idea, I asked a slightly different question. Can we write parts of programs as nothing more than one line transformations? Obviously the answer is yes, but now it's a really good idea because I think it gives us a slightly different way of thinking about programming. Now, before I go any further, anybody who has any sense of computer history at all will know that not a single thing I'm about to show you is new. This is all known technology. Some of it goes back to the 50s or before that. I don't care, right? I'm not claiming to be new, but what I am claiming is that it's a different way at least for me to think about programming and I'm really enjoying what it's letting me do. So I produced a framework. No, it's not framework. I created like two modules that I packaged together. I called it diet because it's based on the idea of reducers. And it lets me play with a programming model. So in diet, all I do is define code as a series of transformation specifications and you group them together into a thing I call reductions and all of the transformations in a particular reduction share a common state. And then you can nest these reductions to write more complicated programs. A reduction looks like input goes to output. So it looks just like the clauses inside a con, for example. It does pattern matching on the input and the output is just an elixir expression. If you want to change the state, then in the output, you're gonna add an extra call, you're gonna say something like update state and give it the new state. And that's what tells it that, yes, I want to change the state and not just use the state. So like I say, inputs are patterns with guard clauses. And what happens is you produce an output and diet assumes that that output is another pattern for the input of something else. And if it finds that, it just invokes that match instead. So it's gonna keep calling those matches until it finds one that doesn't match at which point it's gonna return it to you as the result. So it's kind of like state machines. So let me show you a real simple example. So where I make a fool of myself typing. So we're gonna do FizzBuzz, all right? And we're gonna use this diet thing. So diet dot, oops, transformations. And now I'm gonna define my set of reductions. So my input is gonna be a number. And what I want to return is either that number or if it's a multiple of five, no, hang on, I could never get this right around. Three, I say Fizz, multiple of five, I say Buzz. If it's multiple of both, I say FizzBuzz, all right? So a convenient way to do that is, I'm gonna just call, let's call it FizzB, FB. I'm gonna pass in the number and I'm also gonna pass in the result of remaindering the number by three and remaindering the number by five. Okay, so if I was just to run this, sorry, oh yeah, thank you. Yay, that is the only mistake I'm gonna make all afternoon. So if I was to invoke this and pass in 27, for example, then all that's gonna come back is a tuple, FB2702, which isn't much use. So let's extend that a little bit. So now I'm gonna match FB and I don't care what the number is at this point, but if my remainder is zero for both of those, then my result is gonna be, oops, FizzBuzz, which is actually better, FizzBuzz, right? If the remainder is gonna be zero on the three, then it's gonna be Fizz and if the remainder is gonna be, ah, sorry, zero on the five, buzz. And then the last case is, if it's not gonna be zero on either of those, then don't care, it could be anything. Yeah, let's be consistent. Okay, so that is a program written using that Dart framework, just a series of transformations. Should we run it? Okay, so it's gonna be an IEX. Oh, yeah, that's not supposed to happen. Oops, sorry. Yeah, that's all right, I'll go with that. This worked before I talked to Chris in the green room and showed him just that. It says, actually, 100% Chris's fault. Oh, I really hope this works. It's got some really cool demos to show you. One more time. Yeah, I am, let me go out to, I mean, that's how I run all the time, so I'm not just sure, compilation hangman. That's his fault. That's his fault. Yes. Ah, okay, that's what I get for showing you code just before I talk about it, hang on a second. Hangman model. Yeah. All right, one more time. Don't encourage it, don't encourage it. Okay, so to run this code, I'm gonna need something that steps through all of these cases, yeah? So I have a module called stepper that I feed this thing to and it does the business, right? So let's create a stepper here, so I'm gonna say s equals a bit of ooprogramming. Look at this, stepper.new. I pass it the name of the module, which is fizzbuzz and pay no attention to that last parameter. It'll become clear later on. So this constructs a stepper for me and now I can execute this code. I can say the result and the next version of the stepper equals stepper.run and I'm gonna pass it the stepper and in this case, an input value. So I could pass it, say, let's do two, yeah? And I'm only interested in seeing the result so I'm gonna ask IEX just to output the value of the result. And so if I pass it two, I get a two out. If I pass it three, it says fizz. If I pass it five, I get buzz. If I pass it 15, I get fizzbuzz and 16, you get 16. Right. So that's clearly all you ever need to do to do programming because at the time it returns a num, oh, yes. Because the, it is magic. That is actually a good question because to be honest with you, normally what I do is I actually pass in like a request like that and this time I thought, hey, I'll show off and not do that. So quite why it doesn't loop infinitely. I would just say it's very, very clever. I'll work out why later on, okay? Oh yes, maybe it is because the number is quoted. Thank you. Yes. Details people, details. I don't know. I'll work that one out. I'll work that one out. Okay. So let's make it slightly more complicated and you'll notice that fizzball walls didn't actually use any state and that's okay because I'm gonna show you state a bit later on. So now our next one is gonna be one of my favorite examples run length encoding because again, it's totally useless just like fizzbuzz. So we're gonna take a list of values and if we detect a repetition in those values, we're gonna replace the value with the tuple of the value followed by the repetition count. Yeah? So we can start this one off again over here. Got one, let's go to RLE1.exe. Okay, so we're gonna use the transformation again and kind of shrink this font just a little bit. Okay, so we're gonna start off with a list and this time I'm gonna pass in a command encode list. We can use guard clauses. So if you wanted to, you could say when is list list. Okay, and I'm gonna have to have a helper function here that has an output result. So I'm gonna have my command is gonna look RLE, it's gonna have the input and it's gonna have the output. So if I was to run just this code, right? So if I compile, did I save it as fizzbuzzle? I didn't save it, that's why. This is a bad idea. I don't know why I'm doing this. Reductions do and, and, that's the wrong file. All right, let's try. Okay, so again, I'm gonna create a stepper and we pass in this case RLE1, no initial state, R comma S equals stepper.run encode. We just pass in a list one, two, two, three, three, three, three, four, five, five, why not? Oh, I didn't pass in the stepper itself. And let's just have it just look the result here. So the result is just that tuple I created, right? RLE and that list and then the output thing is empty. So let's start adding some code. So here I'm gonna say, so to run length and code, an empty list and the result, well then the answer is just the result, right? And if I run length and code, let's start with the simplest stuff here. A list that has the head of A and then rest and a result. Then the result is going to be what happens if you run length and code, the rest and you stick A on to the result. Thank you very much. Who needs a compiler? Okay. So back up here, we'll reload RLE1 and there's an obvious bug in this, right? Apart from the fact that the missing terminator is there. Ding, ding, ding, ding, ding, ding, where's my missing terminator? Okay, so I can just rerun that stepper now and the result comes back backwards, right? Cause we're recursing through it. So I gotta fix that by just reversing that. Okay, so that's just copying the list. Let's start running length and coding it. So if I'm running anything and coding a list where the first two entries are the same and then there's the rest and the result, then the answer to that is what happens if I run length and code, a list that contains the value twice, followed by rest and the result. And then the same kind of thing is if I have a list that starts with A and some count and then another A, the result is going to be A and that count plus one and another A. So now I've written my running length and code are using nothing but transformations. So back up here, it's compiled, yeah? So this is clearly relatively artificial code, yeah? It's not like you don't spend every day writing pure code that does something like that. But I wanted to show it to you because I wanted to show you that you can write stuff using nothing but transformations, yeah? But now you might ask, okay, well how do I know what this is doing? Because it's like kind of non-deterministic, but it's like kind of like it just throws values out and then does things with the results and it's hard to track. But the cool thing is you can track it because the execution is actually run by this stepper thing. So if I go back into the command line here, right? I still have my state that I've kept lying around. So I can say diet.debug and pass at the state. And sorry, pass at the stepper, not the state. And so now it says, okay, I'm diet debugging RLE1. The interesting command at this point is if I type T for trace. It's gonna show me everything, all right? This is showing me every single transformation that took place during the execution of all of those runs of that stepper. So what you have is you have input arrow output. So you can see in this last case, we had a five, no, up here, we have the output, sorry, the input list consists of two fives. So it converts that into five two. Now we have an input list that has five two. It converts that so the output has a five two at the front of it and the input is now empty, input's now empty, so we now produce our result. Yeah? So you can actually trace through all of these things and see what's happening, which is kind of fun. All right, you'll be relieved to know that I'm not going to code the next example. So let's have, I'll have a look at hang person because hangman involves having some kind of state. You have to keep track of the guesses and everything else. And so for the first time, we're gonna introduce state into our diet models. And our state contains all the obvious things. It contains the actual state data, which is the word to guess, the letter is used and the turns left. And it contains what you might call business logic. So the fact that you've used a letter is business logic. The number of turns left is business logic. Whether or not we've won is business logic. So in this way, these things are actually a lot closer to pure models in the correct sense, I guess, than we're used to. It's purely just, it's almost an object actually. It's state plus stuff that operates directly on that state. And it's manipulated from the transformations. And the transformations have no state, no business logic. They're simply the rules by which you invoke parts of the business logic. So our model looks like this. It's just a regular old elixir model, def module, hangman model. The structure is where you define the structure of the state. And then we have down the bottom here, we have our quote business functions. Did we already use this letter? You know, I want to use this letter. Is this letter in the word, et cetera, et cetera. And then the first function there is a function called build, which we use to construct a new model given a word. So how do we use that? Well, now inside our hangman module, this is the one that has the reductions in it. We use diet transformations, but we tell it some extra stuff. We tell it that our module, our model, is a module called hangman model. And because we don't wanna type hangman model all the time, we're gonna alias it as HM. So this is simply telling this module the name of the module that represents our model. Inside the reductions, we don't want to, we don't particularly care about the model as much as we care about the state in that model. And so I guess I probably could have called that state name, but it's called model name. And what it does is it basically ensures that within that block, within that do end block, the variable game will already be set to the current state of the hangman game. So this is the actual logic of hangman. So at the top, we come in, we say the model name is game, and the kind of quote entry point is make move. So we say make move and we give it a move. And it says, okay, to make a move, I'm actually gonna call a separate match because it's a three parameter two plus plus a two one, it's gonna make move the move and a Boolean, have we already used that letter? And then the next two lines say, if we have already used that letter, then I'm just gonna return already tried. Otherwise, I'm gonna record the move. And what does record move do? I mean, I could have made those into one, but I like splitting them out. Record move calls update model and updates the letter, sorry, updates the model by saying I've used the letter. And then it returns the tuple maybe match and a move. Just say we'll hate this somewhere in Poland. A little Brazilian is gnashing his teeth and not knowing why. It's because I have all these implicit variables lying around, but you know what? I don't care because to me, it makes sense. And I'm the important one. So how does maybe match work? Well, you pass maybe match goes to the model again and says, is this letter in the word? And again, we come back with either true or not true. If it's true, we have match. So then we're gonna go down to match here and it's gonna say is the game one. And if it is, then we're gonna return game one, otherwise, et cetera, et cetera. So it's really pretty straightforward. And it's nice if you write this in regular code, then the natural way to do it involves at least one level of nesting of cons or ifs or whatever it is you prefer to use. But like this, it's all pure linear code. So let's see if we can run this. Based on my current experience, the answer will be no. All right, so I'm gonna create a stepper and I'm gonna create it for hangman. Oh, yeah, you're right. I'm still in the debugger. Thank you. All right, s equals stepper.new. Hangman. And now I'm gonna, now you're gonna see the parameter at use. I'm gonna pass in the word to guess and you're not supposed to look at it because it ruins the fun, okay? So you'll notice that what comes back is a stepper object that contains a model. And that model is one of those hangman model structures. So it's the word one back guess turns left, et cetera, et cetera. So now we can start playing our game. And we can play the game in a couple of ways. One way is I can do it like this. I can say r comma s. This is the way we've seen so far. Equals stepper.new, sorry, stepper.run. Stepper run s. And then I'll just have a look at the result. Good guess, it says. All right, so that's one way of running it. Or I can use this diet.debug thing. And there's actually a thing inside that that lets me run a step and simplifies the syntax a little bit. So I can just say arm make move o, good guess. Hey, look at this, I'm on a roll. M, good guess. E, oh, damn, I made a bad guess. I hate that, right? Because I don't want to have this history of me making a bad guess. Let's have a look at our history. So this is all of the history of that game so far. And you'll notice that the numbers are one dot something, two dot something, and they correspond to the top level entries into the stepper. And then inside the stepper, there are individual sub steps as it's doing the internal matching before it returns. So my first move was an a, so then it calls that model to say have I already used a, no I haven't. So then it's gonna go to record move, it does a record move, there's maybe match. Maybe match is true, so it's a match, right? Have I won, no, so therefore it's a good guess and it returns good guess, yeah? You'll notice that some of these lines have a little arrow next to them. The little arrow means that I changed the state. And those are the only times I changed the state. And if I wanna find out what I did there, if I just type in that number, was that 1.3, then you can see that at 1.3, my trigger was record move a, the current state of my model was that. The result of the match was a maybe match a, and the changes were, notice it's highlighting the a in guessed. So that says that's what changed in that particular instantiation of the function. If I go down to when it's slightly more interesting, if I go down to 4.7 there, then I now have one less turn left. So turns left was seven and now it's six, yeah? But I don't like the fact that I have this failure in my history. Wouldn't it be nice if we could change history? Let's clone our history, but I'm gonna clone it only as far as 3.8, which was the last time I was successful. You notice that I now got hangman zero and hangman one. If I have a look at the history for hangman one, it only goes up to 3.8. If I switch across to zero, now it goes to 4.8, where my failure is. Let's go back to one because I like that more, right? And now I'll start making some more moves, make move t, I don't need the comma. Good guess, what haven't I guessed yet? W-O-W, I must be getting close to winning. M, I've already tried. Sorry, B, B, one, yay. All right, so between us we won that game. Yeah, isn't that cool? Right, we won and this is in that history change one. If I go back to zero, oops, here's what Donald Trump would call a loser. So sad, I think that's kinda cool. I think it's cool to be able to look at the execution of my program in terms of the decisions it made as it was executing and to see the changes of state explicitly as they are made as the program executes. It's fun to be able to change history, to go back and to clone a history up to a certain point and then fork it off. Imagine you had, say you were running a server and it had some relatively expensive initialization where you had to go out and fetch some whole bunch of stuff and do things to it. So you run that initialization forward to that particular point and then fork it, the history, into separate threads. So that all the subsequent execution is independent. It all shares a history and then becomes independent. So there's really very little magic here. It's nothing special. When you say stepper.new, it calls the build function in your model. So first of all, it goes to the model which in this case is hangman and says what's the name of your model? In this case, it says hangman model. It then invokes the build function of the hangman model passing it whatever parameter you passed in and it then goes and associates that with the state inside the stepper. And then whenever you call step, it passes that state into, in this case, a variable called gain. And that makes it available. If you were to say update state inside that game, then it actually changes that value and sends it back out. And so the stepper function is simply calling into each of those transformations one at a time. So it's running one, then it runs another one, then it runs another one until there's no match. And each time it does that, it can record the state, record the history, everything else. So there is, oh, if I'm channeling Trump, there is a functional alternative fact. And that, it's a big one, right? And it's one that we accept without blinking. We say that pure functional code, and I know we don't write pure functional code, but even so, pure functional code has no side effects. And that's what makes it so great. That's what makes it so easy to work with because the data doesn't change because there's no side effects. There is a side effect, and it's an incredibly big one. In every single bit of code that we write. Anybody give me a guess? Think of some piece of state that changes constantly throughout the life of this code. Sorry? And how is time expressed in code? Yeah, it's the program counter, or if you're an Intel person, the instruction pointer, right? But implicitly, our program counter is being changed every time we execute any code. And that change is a fundamental side effect. It's a fundamental change to the state of our program. And it's one that cannot be undone. And that's what makes it so hard to go back. That's what makes it so hard to split our code up at any kind of level that doesn't involve modules and functions and us explicitly telling it to split it up. But if we were to go back to this kind of scheme, when we're running these things here, each time we make a guess, well, not even each time we make a guess, each time it runs one of those transformations, it goes into the code and then comes back out again. And that is a stateless operation. So I could go back in and re-execute that and I would get exactly the same result back every single time. I can copy it and have something else run it. I can run it on totally separate processes and it will run perfectly happily. So this idea that I can just keep this chain of history and if necessary, clone it like this, I think is a really big deal. It's not new, right? The basic ideas here are things like state machines, tuple spaces, that kind of stuff. Databases like Datomic do exactly this to handle changes, right? Nothing new. But Elixir makes it incredibly convenient to write code this way. It gives us all of these interesting side effects because each one of our transformations is fundamentally a single line of code. And I get reuse at that level. I get per line of code parallelism. So I could have my stepper running as a pool of gen servers and if I had a hundred people playing Hangman, then individual lines in that transformation would be running randomly on all the different servers and it wouldn't matter. Per line of code parallelism. You'll notice I was live reloading the state machine as the code was running and it was fine with it. Now, special case because there were no changes to the data structures, but in principle, you get live reloading not just of code, but also of the flow in that code. And obviously you get world peace. So I'm not saying we should throw away our cons and throw away our depths and write everything like this. As I said halfway through, this is not the solution to the universal solution to programming. Nor do I think this is particularly large scale practical. But Jim, for example, was talking about a thing he had where he's writing some code that is going off to some, I guess it's an OAuth server and getting back a token. And then if that token is okay, he's gonna use that to make a couple of subsequent queries that have to go in order and then a few more queries can go in any order. But if at some point that token expires, he's gonna have to go back to the top and re-get a token and repeat the whole sequence. Now, if you're a writer using regular statements, if statements and everything else, you're gonna end up with a rat's nest of code, right? It's gonna be really ugly, particularly the case that says I'm three levels deep and then suddenly the damn token expires and I gotta abandon the whole thing and go back up the top, right? Just think for a second, how are you gonna implement that? You might even end up sort of throwing and catching or something else to handle that, ugly. But that's totally linear, single level state machine code. And it would be represented in half a dozen lines as those transformations. So there are definitely times where this is valuable. But more than just being valuable, I think it's really interesting to think about this as a different way of thinking about your code. I don't wanna think about my code as what I'm telling the computer to do. I'm gonna think about my code as being transformations that operate on this big pool of state. And it chooses the appropriate transformations based on that state. And then I can nest those to build real world applications. So let's not get fixed into this idea that Elixir is kind of like a better Ruby or a better Rails in Phoenix or whatever else. But instead, let's use some of these wild capabilities, the fact that processes are basically free. The fact that I can just throw around modules and data independently. And let's start using those things to think about different ways of coding, different ways of programming, structuring our thought as well as structuring our code differently. And hopefully along the way, we'll have some fun. Any questions? Thanks, Dave. That was fantastic. I think you blew everyone's mind with that debugger. That was super neat. I just had a question about the history and the changes. As far as applying this to some real world problem like that Jim had, I think it's really neat on the small scale, but do you have any mechanism building currently to truncate history? Whereas if I have some really, really long running thing, I'm gonna have to discard that at some point and what kind of mechanisms you think that would be good fit for. Okay, so this code, I finished writing like two days ago. This is this part. And it's really pretty trivial. It's just an experiment. So the next step is gonna be to take this and actually try and use it for real in something decent size. And that's why I'm gonna bump into issues just like that. I'm gonna run it into performance issues. And I also wanna generate this pooled solution where I have a pool of servers that are exiting the code. And I believe that to get any kind of decent performance, I'm gonna have to implement some kind of affinity so that when I'm inside one of those runs, the steps happen in process and then it goes out of process when you invoke it from the outside. So that's the kind of thing I'm gonna be addressing. I think that there is no intrinsic mechanism, sorry, intrinsic reason why you have to keep history. Yeah? So you could say, for example, I'm only gonna keep the last thousand individual transitions. Then I'm only gonna keep the previous thousand of the high level transition. So I'm gonna ignore the internal ones and just give the entry and exit to the calls to run. And after that, forget it. Or whatever I might do, that's tunable. So I could do something like that. It's not that I'm like, I say a detomic where I'm actually rolling up all the individual deltas. I'm just recording the history and then doing diffs on it to show you the changes. I've noticed it's the speakers that ask questions. We should ban that. Could we extract the debugger and hook it up to function traces to use it on normal gen servers? The debugger is a total of 25 lines of code, honestly. But yes, if you wanted to. It's, hang on. Okay, more than 25, but not much. Okay, so there's trace history. And clone is C, so that's up here. There's the clone. Yeah, I mean, so it's not much. But feel free, I mean, at some point, once this is stabilized just a little bit, it's currently up on GitHub, but it's private because the last thing I want is people saying it doesn't work. I know it doesn't work. But once it's stabilized a bit, I'll make it public and feel free to mess around as much as you want. Okay, I want a non-speaker question and I'm gonna be very patient on this one. Because remember, we've got until midnight. Did you just reinvent prologue? Sorry? Did you just reinvent prologue? Yes, I know. I didn't say it was original, but it doesn't have any backtracking. So no is the simple answer. It's closer, in fact, to tuple space. You know, what you're doing is you're matching on a particular thing and then generating, generating some output based on that. But it's tuple space with executable code. This kind of reminds me of Wolfram's, you know, cellular automatons, right? Just repeated tasks, complex things. Yeah, I mean, the cellular autonomy is basically a state machine. And that's fundamentally what this is. Or the Elm reduction model. Or the Elm reduction model, exactly, yeah. And that's, in fact, why Elm can do things like go back in time. Because it is exactly this. Okay, let's give him a huge round of applause. This was mind blowing.