 Welcome to my talk, Rewriting Rack, a functional approach. Thank you so much for coming. I'm super stoked to be here, and I'm glad you guys all made it. I'm Alex Wheeler. You can feel free to tweet me, ask me any questions, comments, or concerns at Ask Wheeler and see what I'm working on at Alex Wheeler on GitHub. I currently work at VTS. We do commercial real estate asset management software. We have just over six and a half billion square feet managed on the platform, and it's all powered by Ruby. But I started thinking, right, and if we had built VTS, say in 1956, we definitely wouldn't have used Ruby. We might have used something like this. We might have used Fortran, right? This is a Fortran punch card. And what you would do here is you'd punch it in an instruction set, you'd hand it off to a huge machine like this IBM 704, and a few hours, days, years later, you might get a result, right? Given that you had no bugs, you didn't spill any coffee, you didn't rip your program, you'd get some feedback and this was cool, right? This was high technology at the time. A few years later, we have John McCarthy. Now McCarthy's the father of Lisp, and McCarthy's work was a little bit different. So Fortran, it worked super well for these large organizations that they kind of knew exactly what they needed to do. Their problems weren't that complex. And McCarthy's work, he was doing artificial intelligence. So it wasn't so much that he even needed the solution, he just really needed to find the questions to ask. And for that, he needed a much more interactive computing environment. He needed to be able to ask questions and get instant feedback. He couldn't wait to go hand off a punch card to a machine. So what he did, he went and he built Lisp. And with Lisp, we got the first REPL. So if you ever opened up an IRB session or Rails console and got in that instant feedback, that comes directly from Lisp, the technology implemented in the 50s. A few years later, we have Grace Hopper. And Hopper led the team that built the COBOL programming language. And if you've never seen COBOL, here's a simple function. It looks a little different than the punch cards we saw a few slides ago, right? It reads just like English. And this was a huge idea for her. She wanted to build software using languages that read just like English. Let's fast forward. 1993, the public web came out. We also learned earlier in the keynote, this is when Matt's first started working on Ruby. A few years later, the first public release of Ruby. Super cool, almost 25 years ago. And then 2004, we see Ruby on Rails. This is probably when many of us first started writing Ruby, right? We were building web applications in Ruby using the Rails framework. And here we are today. It's 2017. And you guys have all decided to come and watch me speak for 40 minutes, which is super cool. And the future's now, right? Like we've figured out software development. We write perfect programs, they never crash. And you know, that's a lie, right? You're all laughing, that's a total lie. We haven't, we don't really even know what we're doing. The industry is just way too young. But what we do have is we have Ruby. And the genius behind Ruby is that it wasn't built in a vacuum, right? Matt's actually looked to the past at some of these technologies we just went over and he combined them all into this language that we decided to come together in New Orleans and talk about for a few days. When I tell my friends that, they think I'm crazy, but we really enjoy it. So what were some of those influences, right? Well, small talk, we see influences from Lisp, we talked about IRB, Perl, and even Python. So has anyone seen any of these before? No, not if you've written perfect programs, but if your program's ever raised an exception, you've probably seen some of these. And if you weren't aware, these are actually Python exceptions. These come word for word from the Python programming language. So we can thank them for that. Or how about some of these? These are our cryptic global variables. And we did borrow these from another language as well, right? For better or worse, we got them from Perl. Super cool. And if you are gonna use them, please require English. You'll get much better references. They're no longer cryptic globals, they're just globals. And blocks. If you've done any Ruby programming, you've probably iterated over some kind of collection with map or each, and you've passed it a block to yield each value into to do something with it. And this comes directly from a Lisp. From the 1950s, we have real closures in Ruby, right? We have a block of code bound to some locals that we can take and we can execute it in different contexts. And last but not least, this is probably one of my favorites. I remember when I saw this for the first time. This goes back to what Grace Hopper was talking about with really readable programs. 10 times do something, right? But what is a show? The show's in Ruby. We don't really have true primitive types, right? Everything is an object. We talked about in the talk before that there are no rules in Ruby. And that's true, right? We could go in, we could open up the integer class. We could redefine the plus method to actually be minus, and in our new world, three plus one equals two. And that could either be really exciting for you or really scary, but it's pretty cool. And that's actually an idea that came straight from Smalltalk. Smalltalk was the first language where everything was an object. And we took that and we have Matt, who took all these amazing ideas and baked them together into one extremely fun program language. Now, we look at these, right? And we start thinking, like what do they all have in common? So maybe not this one, but the rest of them. They all aim to simplify. We don't build new programming with your technologies for fun. Some of us do, but for some of these larger projects, like languages, we're trying to make our lives easier. And Matt's talked about this earlier, right? When he said, I wanna push more on a machine and have the humans do less. And that's because programming's really tough. If anyone tells you differently, they're a liar. This is a really hard profession. And we're only human. And you've probably heard this number. It's a plus or minus seven, right? It's a number of things that we can keep in our head at one time. So I have a question for everyone. And how many ways can we call this proc? So let's go through them, right? We can call it and pass it one. This gives us the same exact result. We have a little more syntactic sugar. You can call procs this way as well. You can send the call message with the argument one. We could be a little stricter. We could use public send. So if it was private, it would blow up. And procs don't enforce the area of the function, right? Of that closure. So we could pass any number of arguments and it's still gonna return the same exact result and ignore the rest. It's really similar to like a JavaScript function. And this sort of takes us to this idea of functional programming, right? Functional programming really at its core is about two ideas. It's about organizing our code around functions instead of objects. And arguably more importantly, it's about having immutable data. And you might not even know what those two things mean. But that's okay because there's a much bigger idea at play with functional programming. And it's to simplify. It's the same thing we've seen since the 50s. We've been building technologies to simplify how we build our programs because we know programming is really tough. And that brings us to closure. So I kept hearing about this functional programming idea from friends and colleagues and talks similar to this and blog posts and I didn't get it. It sounded like this crazy advanced concept that you would only use if you're building, I don't know, a distributed blockchain mumbo jumbo. And I was never gonna use it to build my crud apps. So I said, you know, I need to go figure out what's this thing all about? And I looked at closure, which is a functional language. It's also a Lisp that's implemented on the JVM. And let's go through a few examples, right? Because what happened was I went over to closure land and when I came back to Ruby after re-implementing a lot of my programs in closure, it clicked and I said, I get it. By using these functional languages we get rid of an entire array of problems. Like they are literally impossible to encounter in these sorts of languages. So let's look at some basic closure. You're gonna learn some closure today. So here's a basic closure file. You'll see that we have vectors. So a vector is just like a Ruby array, right? Here we have a vector of four integers. We have maps where key value pairs very similar to a Ruby hash. We can call functions. So if you've never seen a Lisp, please go experiment with one because they're the most interesting programming concept ever. But when we wanna call a function, we wrap our code in parentheses. So here we're gonna say give us the value keyed by name. And if you look in the bottom left corner of the screen down here, you'll actually see the result of the line of code that I'm on. So here we get back Alex. We can go down and we can do basic math. So we execute this and we get two. We can have nested function calls, right? So here we're multiplying two by two and we're getting four. Here's a basic function definition. So we're defining a function called full name. It takes two arguments, first and last, and then it concatenates them together. And we can go see what that looks like, right? We're gonna hug it in parentheses. That first symbol on the left side, full name, that's in function position. So that's always gonna be a function and anything after it are arguments. Now that's not the most interesting part though. That looks like many other programming languages, right? So here we have a var. This is kind of like a Ruby variable. We're gonna say we're gonna bind to conf this map. And let's see what we wanna do. We wanna change the name from Ruby conf, where we are, to closure conge. So let's see what happens, right? So we associate with conf the names key to closure conge and what do we get back in return? We get back a new map that says name closure conge. But then we go ask that var for its identity and this says like what are you? Who are you? And we see it's still Ruby conf. And this is the key point to closure and a lot of functional programming is immutability. You cannot change a variable once you've assigned it a value. We're working with values. You cannot, the language will not allow you. Let's see one more. Here we have a vector of two maps, Ruby conf, closure conge. We have pop, very similar to Ruby. We get back Ruby conf. We check the identity and it's still that full vector, right? And this is the key idea here. So that's the 101 to closure. If you wanna learn more, I highly encourage it. We can talk after the talk. Now I love this quote by Alan Kay, right? Alan Kay is one that gave us that idea of everything is an object. He says a change of perspective is worth 80 IQ points. And so by the end of this talk, what I'd really hope is that we all become a little bit smarter. What we're gonna do is we're gonna go in and we're gonna examine rack from the perspective of a functional language, like closure, and see how can we avoid really simple problems. Like problems that don't exist in some other languages. And it doesn't matter if you don't know what rack is because I'm about to tell you. So rack is super simple. On the left, we have a web browser, Google Chrome. And on the right, we have a basic Ruby web application. A lot of us use Rails, right? And in the middle, we have this thing Unicorn. And Unicorn is a web server and it has the most boring job in the world. It sits there all day and waits for requests to come over the wire. And then it hands it off to a web application. The web application does something with it. It then hands it back to the web server, which knows how to parse it back into plaintext and send it over the wire. So plaintext in and we get plaintext back out. And there's not just Unicorn, right? There's many web servers. There's Unicorn, there's Puma, there's WebRick. They all do the exact same thing but they have their pros and cons, right? They have their strengths and weaknesses. And as we know, there's not just Ruby on Rails. There's Sinatra, there's Hanami and there's probably thousands or countless other home-baked solutions. And what would be really cool is if today we were using Rails but tomorrow we wanted to use Sinatra and we didn't have to change really any code that interfaced with the web server. But today we're using Unicorn and tomorrow we wanna use Puma. We can just swap them out willy-nilly. This is what Rack solves. Rack is a web server interface and it has a few very simple rules. It says if you're gonna be a Rack compliant Ruby web app, the entry point to the application where you're gonna get this request, you're gonna define a method call. It takes the request as an argument and if you're a Rack compliant web server, you're gonna call that method and pass it the request as a Ruby hash. Now, Ruby library, you need to return a triple. So an array with three values. The first value being an HTTP status code, the second being a hash, which are key value pairs representing any headers and then the third being the body that responds to each. As long as you do that, we can all work together and swap in and out, right? So Rails, Sinatra, Hanami, your basic Rack applications, they all have this entry point, which is call. So what's the most basic Rack application we can build? This is pretty easy. We have a class called app. We could call it anything we want. We could call it Alex. We could call it Ruby. We define a call method, which takes the request, which Rack likes to call the env. You could call this anything, call it request. And it returns a triple, right? And how would we run this? Well, we require Rack. We initialize a new instance of our app so that it can respond to call. Now we pick Unicorn, so we pick this adapter. We say, hey, we're gonna use the Unicorn web server and we run our application. And if we want to use Puma, we just swap in the Puma adapter. If we want to use Webbrick, we just swap in the Webbrick adapter. So assuming like this code is in a file called serverRB, you could call it whatever you want, we can run it and we'll see here. Sweet, that's a Ruby web app in three lines of code. That's pretty awesome. But we could do better, right? What also responds to call in Ruby? Well, procs do. A proc responds to call, it can take arguments, and it can return a triple. So that means that we could actually build a Ruby web application in one line of code. And this does the same exact thing. Now we can also use a Lambda. Lambdas are procs, but they enforce their arity. So we have to pass the correct number of arguments or an argument error will be raised. And this is the same exact thing. But it's pretty cool, right? We can build a Ruby web app in one line of Ruby. Now, Rack gives us another alternative if you wanna go a step further. It gives us a DSL for crafting these. So we get this RackBuilder class. And what we can do is we can initialize a new builder. We can say, hey, we wanna use this app, and then we're actually gonna run that. Now you're probably looking at this and you're saying like, Alex, what is that, doesn't provide anything better than our proc we were using. Well, the difference here is that we can use this thing called middleware. So we can say, hey, use the logger middleware and use the cache middleware. And middleware, it's also a very simple idea. At the bottom of the screen down here, we have our Rack application and outside on the top where we can't see, we have a web server listening for request. And along the way between our web server receiving the request and our Rack application receiving the request, we might wanna do some stuff, right? We might wanna log something. We might wanna actually just go to our cache instead of ever even hitting the Ruby app. I might wanna do loads of things, right? And how it works is it's like a stack. A request comes in, our web server's parsed it in to a Ruby hash, it gets passed to the first middleware, it logs something, it gets passed to the next middleware. In this case, this middleware actually attaches something to the request. So maybe the URL to some cached asset or something. That gets handed off eventually to the Rack application and it responds. So let's look at how middleware is implemented, right? It looks very similar. We have a class, we're calling it middleware. And it has to define a call method taking the request just like a Rack app. But the difference is it needs to know about the next middleware or Rack app in the stack. And so it gets initialized with the next application. So how that works is that we initialize the next application, every time a request comes in it calls the call method passing at the request. We can do something with the request and then we just pass it on to the next thing in the stack where it's eventually gonna end up at a Rack app which will respond and it'll get sent back over the wire to the client. And that got me thinking, right? What is this Rack builder really doing under the hood? If you go do a word count on it, it's 171 lines but there's actually a lot of really good comments. So without comments it's 79. Still like a fair amount of code. And so let's go through, let's actually see what it's doing. Here, we have a new method, we have use, we have run and we have to app. So we have these sort of four main methods. Here's the actual code. We're gonna spend the rest of the talk, no I'm just kidding, don't even worry about reading that. We'll go through a simplified example. What does initialize do? It makes an empty array. That's where we're gonna store all of our middleware that we keep wanting to use in our stack. Every time you call use, it takes the middleware, puts it in a block and pushes it onto that array. When we say run, that says hey, we wanna use this Rack application that responds a call and returns that triple. And then two app goes through and it actually constructs the stack. So here's the actual code. It's just a reduction. We're gonna go through this right now a little bit slower but what it pretty much does, it waits for a rack application and it's gonna pass it on down that chain until we get a fully composed rack app and then we can pass that to the web server. So let's see what that actually looks like, right? Let's imagine we're using two middleware, logger and cache. So we've called use twice, our array has two values. So we have logger and cache and they're waiting for that rack app because we need something to be the tail end of the stack to finally respond to the client. So what do we do? Oh, okay, we have a rack app and it gets passed to cache. So now cache gets initialized with our rack application and then that whole stack, which is the whole composed stack so far, gets passed in to logger. So now logger knows about cache and cache knows about the rack application and here's our built app. And I love playing with syntax and having fun programming so if we rearrange this a little bit, it'll still be valid Ruby but we're gonna move things around a bit. We move cache down and we move our rack app down and you'll see this is our actual stack, right? It maps one to one. There's our logger, there's our cache and there's our rack app and that's what's gonna happen when a request comes in. So that begs the question, right? Can we do functional programming in Ruby? And I think we can, right? We have the tools to do it and if we're gonna use functions instead of classes, could we implement this without any classes, right? Well, we could and how we do that in Ruby, we don't have first class functions we can pass around but we do have this idea from lists, right? We have these blocks and we have prox, right? Which are local variables bound to a context that can be executed in any other context. So let's see how we can do this, right? Here's our logger. So we need an initialize method. Okay, well, you see the prox, pretty simple. Initialize takes an app. Well, our prox, it could take an app. What do we need? We need a call method. Okay, well prox respond to call so we just return another prox. We need to take in the request. So we take in the request. What do we need to do? Call that app. Well, we can do that, right? We have a closure around it. Now we can give it a name. The class has a name. Our logger has a name. We can enforce the arity now. So we're gonna use a lambda instead of a prox. So here we go. Here's our middleware implemented without any classes. We're just using prox. Are we safe in here? Who thinks we should keep going? Okay, we're gonna keep it going because we just implemented this without any classes and everyone's about to have their mind blowing. So we can do the same exact thing for the cache middleware. We can do the same thing for our app, right? And we have our builder. We've just implemented this with just using prox and that's kind of cool, right? We could pass our app to say we wanna use the WebRook web server and we could actually run this. And then we could start having fun with it, right? Ruby, we learned in the last talk there are zero rules in Ruby. We can replace our due ends with C-style blocks. We can actually use this other syntactic sugar way to call a proc. We can actually wrap everything in parentheses. Ruby, parentheses do not matter. We can compact it to make it a little more concise. And then what we can do is we can compare it to the closure solution. And it looks nearly identical, right? Let's just pull out the middleware part. So here's our logger and cache. This is closure. Just kidding, this is Ruby. I got some of you. I saw some people like, yeah, he's right. No, this is Ruby. And let's actually look at the closure. It's almost identical, right? So now you're thinking, you're like, Alex is out of his mind. And I might be a little out of my mind. I like having fun with this sort of stuff but I actually think there is a point here, right? Do we need that DSL? Do we need rack builder? Do we need 171 lines? What if we could just go and delete them? That would be really awesome, right? What if we ran a word count and that file actually didn't exist? I gotta quote the late and great CTO, Carl Baum, he's a CTO of our company. And he really instills us in everyone on the team. Code is a liability. As developers, we love to write code. But at the end of the day, if we could solve us something without writing code, that's a real win. So we just saw one, right? We can implement all these things without this DSL. Maybe if we programmed it in more functional style, we could eliminate some code, right? By just using simple functions. But let's look at some more. The two I wanna look at are shared mutable state and also functions instead of objects. And the first one, shared mutable state. This one's super interesting, right? There's lots of bugs that are really, really hard to track down and really annoying. Like bugs where you just kinda wanna go home and take a nap, right? And then there's other bugs. The type of bugs where you say I'm quitting my job and gonna go soul search for a while. Or I'm gonna throw my computer out the window. And these are bugs that come about because of shared mutable state. So let's look at some real world examples. Like these are really simple problems that we can run into in our Ruby programs that would be impossible to run into if we're using a functional language. And we're gonna do it by building some rack middleware. So here we have some middleware that has a call method, right? And let's think of a crazy scenario. We're building this for a courthouse, right? And we have a verdict come in and we need to format it into a message to send off to some system or to the judge, to the jury to read off the results of the trial. So what we wanna do is we wanna actually update the request. So as that request comes in, we wanna add a new key to the hash. We wanna say, okay, the verdict message is equal to whatever the verdict message function returns. So we need to find a verdict message which takes in a verdict which we're reading off the request, right? And it's very simple. It says if the verdict's guilty, return is guilty. If it's not, then return is not guilty. And that's all good. And then product comes around, they go, Alex, Alex, Alex. Alex, Howard, we don't know their name. Oh, we need a name, okay. So the request also has a name attached to it. So we take in the name now and we say if the verdict's guilty, name is guilty. Otherwise, name is not guilty. Sweet, I'm going home, going surfing. And then product comes back, right? They say, Alex, you gotta come back from the beach. There's another problem. Not again, what do you want? And it turns out we forgot that they might actually have a Mr. Oh, no, no, no, no, no, no. That's the next example, excuse me. So then we forgot that they're actually sending a full name. One second, did you hear that? Can we get a big round of applause for the social? Thank you, man, who just said that for looking out for us. Thank you, you told us that, thank you. Sorry about that. Anyways, so then what happens is that they're sending in full names and they just want the first name. So what do we do? Well, we split on white space and we take the first value. We take their first name. It's all good. And then they have Mr. and Mrs. and Miss and this code is getting out of control. So what do we do? And this is a pretty common thing because as programmers, we wanna be lazy, right? Well, we just say, okay, we're gonna make a new method. We're gonna extract a method that's just gonna deal with the format of the name, right? Cause the shape of this is starting to look a little scary. And what's the easiest way to do that? Well, we don't wanna have to pass name down all these functions. So we just abstract an instance variable and then we can just call that, right? So we still have our verdict message but we have a new helper method called name which takes no arguments. It just, we have this global instance variable to this instance that we can read off of. It passes code review. It passes continuous integration. It gets merged. And what do you do? Big corporate high five, right? Look over and give him the high five. And it's all going well. We can actually see an exemplary. We're actually running this. I took a gif of this. It's kind of fun. You'll see we'll put a name, we'll put and it should return what the formatted message is. So here, Alex is innocent. Alex is not guilty. And Jordan's guilty. Jordan's also sitting in the front row up there and he's one of my coworkers. And that's all great, right? Super simple. But then we're using rack, right? We can use more than one web server. And we're getting lots of users at this point. So we switch off a web brick and we go and use Puma. And our code's still passing, right? Everything's all dandy, but Puma is cool because when we scale it's multi-threaded. Requests don't have to wait for the request ahead of them to finish. They can just start running. And let's see what happens here. This time we're gonna run it with two threads. And I've also introduced a little sleep functionality to sort of mimic a long running maybe database transaction. So one of our requests is gonna get stuck. And the other one's gonna be able to keep processing cause it's gonna get handled on a different thread and let's see what happens. So this one's gonna wait. So we get Jordan's not guilty. We should get Alex is guilty. We get Jordan is guilty, right? And this was all good in CI. This was all good locally. But what's the problem here? These are one of the bugs that makes you wanna throw your computer out the window. These are the bugs I was talking about. Let's figure out what the problem is. In Ruby, we get an object ID for every single instance of an object. We get a unique ID. So what we can do is we can actually just, we're gonna log that and let's run our program again. We'll see on the right, we'll see it getting logged. Okay. Do you notice anything very interesting about this? They're the same, right? They're the same exact ID. And the problem is here in this name. When you construct your middleware, your middleware gets initialized once. So that class gets initialized once, but call will get called every single time a request comes in. This is very easy to gloss over. And I've made this mistake before. In Ruby, we go to production and we just built some really horrible software. We might've just sent someone to jail by accident. But in something like closure or functional language, this is literally impossible. It's impossible. The language will not allow you to do it. And what do we do? We throw our computer, not at the window. We break it. We're done with software. Like we're going home. But we have solutions. We have lots of solutions. Here's a very common solution is problem in rack middleware. Every time a request comes in, we're just gonna dupe the object. So we're gonna dupe that middleware, make a new instance of it. We're gonna copy it and then call a helper call method. And that's fine, right? Like it works. We don't have the shared middle state anymore. We're working with different instances. Every time a request comes in. But then you go look in the rack source code and you say, how many times did they call dupe? 29. And that's 29 times that they remembered to call dupe. Who knows? We might've missed a few. Think about your application. The rack source code's pretty small. I know at VTS, we have a massive application. How many times we've forgotten to call dupe? I'm not gonna even go there. And how do you know if you forgot to call dupe? Well, we have a solution for that. It's called freeze. So in Ruby, you can freeze an object. And if you try to mutate it, it actually raises an exception, right? It says you try to mutate this frozen object. And rack builder, that DSL, it says, you can call freeze app. And what it does is when it's constructing your application, it goes down and freezes every single middleware so that if you do mutate something, an exception is raised instead of sending someone else to jail, right? And that's cool. And then you go back to the rack library, right? And we go run a word count. We get 96 times they call freeze. And there's various reasons for this, right? We talked about having mutable strings in Ruby, there's performance benefits to doing this, but 96 times. And once again, that's 96 times that we remembered to call freeze. That's horrible. So let's look at another example. Functions instead of objects. So data, right? If you think about data, it's just facts. It's just a thing. It doesn't really have behavior. But what we do to it in object-oriented land, right? We attach behavior to it. We're like, oh, Alex's name is Alex, but it has behavior, like change name. And no, it's just Alex. The name is just Alex. It doesn't have behavior. So what do we do? We want to model a person, right? We make a class. We give it an add a reader name and we initialize it with a name. You want to make a company? We do the same exact thing. Pretty straightforward. We can make instances of a person, of a company. We can update the person's name. So we have a method update name and we give it al and we check. We check that person's name and it's al. Now we update the company's name and what do we get? We get a no method, right? This method's undefined. Because what happened is that your coworker went and he called or she called this method something totally different. And here we see it, right? It's impossible to enforce this. Everyone has different styles of programming. And what Clojure says is we don't have classes. We're going to use very simple data structures. Mostly we're going to use maps. Maps are great. It's a great associate of data structure. We have keys that point to values. So here we have a person with a name Alex. We have a company with a name VTS. If you want to update Alex's name, we use a source. If you want to update a company's name, we do the same exact thing. And that's not a very concrete example, but let's look at one. So we saw that hash that rack gives us a rack web server and we have another abstraction. We can use the rack request. And what this is, it's a convenient interface to a rack environment. So what it looks like is we just initialize it with the request and we can do stuff with it. So what we have here is this is like the Equahax middleware. And what this does is it goes and we want to actually scrub out any social security numbers that people are putting in. And how we do that is with a private method called sensor social security number. And we just update, right? So we can call on request. We can get its params, which gives us a hashback. When we say set the social security number to the censored social security number. So if you give a social security number like this, I hope this isn't anyone's social security number. You get back a censored social security number. And it's all good, right? We go to production again. And what do we do? We celebrate. We're like, hell yeah, bring in the interns. You know, like this is great. And here we go. We go into production. We're going live. Let's see what happens. Oh my gosh. That's the social security number. That was supposed to be censored. And it's not. And once again, that's the social security number. I'm really sorry. Go change it. So what do we do in this case, right? We go to jail. Like you're going to jail. Like you're going to jail. Like you're going to jail. I'm going to jail. This is out of control. And why are we going to jail? What is the root cause, right? Request params, returns a hash. I know how to update a hash. I've learned this the first day I learned Ruby. No, they've gone. And they've said, hey, you need to call update param. And I was there playing on IRB, and it seemed to be working. I was getting a hash. But no, that actually returns a copy of the hash. And that's fine if you're expecting that, but we're not expecting that. We need to go learn this new API every single time we want to work with some instance. So what do we think about this, right? What do we think about these classes when we could use simple data? Like no, let's not use that. That's confusing. That's tough. How about person? No, we don't need that. Company, no. And the madness. What's a better abstraction, right? We have hashes. I feel like Closure Maps, right? They're associate data structures. They're very powerful. Want to represent a person? Cool, hash. A company, hash. Rack Request, 487 lines. I'm sure we wouldn't be able to get rid of all of those. We'd have to pull some of them in if we wanted to use these pure functions, but maybe we could. 96 times freeze in the rack library. How about zero, dupe, 29 times? What's a better number? Zero. Like zero is easy. You don't have to do anything. So the question is, does OO solve all of our problems? No, it doesn't. We just solve. We saw tons of problems that we could totally avoid by just using a different language. But will functional programming solve all of our problems? And I don't think so. Probably not. If you were going to ask me today what is the future of these languages, I think we're going to find somewhere in the middle, right? I think that object-oriented programming is a great way to design software and reason about it. But if we could get some of these benefits from functional programming, we'd end up in a really good place. So what I challenge everyone here to do is to go out. You need to go look at these other communities. If we're going to build better software or one day actually know how to build software, we're going to have to look elsewhere. We have an amazing community here with loads of talented people. And there's lots of other communities with loads of talented people. And the only way we're going to do it is going to be by looking at those communities. But most importantly, we need to have fun doing it. Most of us got into this industry because we thoroughly enjoy programming. I want to be up here speaking or doing weird stuff like implementing Ruby as a Lisp. If I didn't find it extremely fun. So while you go out and you're searching these other communities, have a lot of fun with it. And thank you. That's all I got.