 All right, so welcome to Steal This Talk. Today I hope to give you some of my thoughts about how we can make Ruby more fun by learning from other languages that have also tried to make fun a priority. A couple of notes before we begin. It is impossible for me to sit up here the same conditions that make me really well lit and have this natural glow also make it impossible for me to see anyone in the audience. So if you have your hand raised or anything like that I'm absolutely not gonna be able to see you. So please feel free however to use my Twitter handle to tweet me questions. I will get a buzz on my phone when that happens and I will respond to the question that way. Hopefully that will make for a engaging session as people have questions. And if I don't get to your questions during the talk I promise that I'll respond to you on Twitter afterwards. If you hate Twitter or just don't wanna use it or don't have a Twitter account, that's 100% fine too. Please feel free to email me and I'll also respond to your questions in the same way. All right, so just really briefly about me. I work for a wonderful company called Pivotal. We have some of my fellow employees up here in the front two rows. Props for fellow pivots. And what I do there is work with enterprise companies who face a lot of challenges with how they build, deliver, develop and design software because doing that is difficult. It's hard to write software. But when we're doing that we get exposed to a lot of different ways that people write software or a lot of different languages, a lot of different assumptions about what is and isn't okay to put into software. And that I think has formed the basis for this talk and that it's exposed me to a lot of different ways of thinking about software and the tools that we use. So I'd like to cover basically four themes. One is where do we come from as Rubyists? What are sort of the ancestors to Ruby and how have those languages influenced not only Ruby but also the broader programming language community? Second is given that sort of background tapestry where might we be going in the future? What aspects of Ruby were probably gonna be around for the long haul? What might be more flexible and change? And this is all speculation, of course. I'm not a dictator with respect to the direction of the Ruby programming language. But what could we speculate on and what are some of the things we're seeing in other programming languages that might be applicable or useful for Ruby to adopt and also in the same spirit as Ruby's current set of features and goals? And then what are some ways that we could get there? So I'll show you some reference implementations of these ideas that are certainly not production ready but there's a GitHub repo for it where I basically modified parts of the Ruby source code to produce these effects or some facsimile of them at least. And then I'll close with a discussion about how we can do it and to hopefully motivate you to try out these sorts of experiments on your own. All right, so let's start with where we are right now. So the best way to kind of understand where Ruby might be in the future is to look at how it got where it is now, I think. And so let's maybe go back to the history books and they say that he who, there's a powerful quote for George Santayana where he said he who does not learn from history is doomed to repeat it. So let's make sure we learn our history with respect to Ruby. So we use programming languages for all kinds of reasons. Some people wanna just do work and clock in and out. Some people are using it for fun. Some people are using it to do science. And some people are just using it because Ruby is fun to use. So we use programming languages for all kinds of reasons whether that's self-discipline or to like people or anything in between. But that means that programming languages are not, they don't exist in isolation. They're as much cultural artifacts as they are technological ones. They're a product of the time, the assumptions that were made when they were designed, the problems that their designers faced in the moment and so on. And of course in the people who actually contributed to the language and wrote it in the first place. Now the chief advantage of programming language over programming languages over other kinds of tools is that they're malleable, they're mutable, they can change over time. If you mass produce a million screwdrivers and then it turns out that those screwdrivers were actually going, were supposed to be hammers, like that's a big problem. You just wasted a lot of metal for no reason. But programming languages don't have this problem. There's, or we don't have it as strongly because if there is a problem we can change it, we have the power. So there's a question of can we make those properties better for the tools that we have? Is there a way that we can get there? And if so, how? And in order to explore that, I wanna explore another programming language that's much older in Ruby, but is also still around. And it's the Massachusetts General Hospital Utility Multi-Programming System for mumps for short. And mumps is pretty much the opposite of Ruby in the sense that Ruby is what we might call a general purpose programming language. Mumps is general purpose with respect to it's picturing completeness, but it is very specific with respect to the kinds of business problems and domains it's trying to solve. So it turns out nobody likes saying the whole acronym, so it actually now goes by M. It turns out that mumps being used in a medical field, naming it after a deadly disease was not the greatest thing in the world. So mumps looks really archaic by today's standards. If you sort of rewind the clock and you could time travel back to 1966 when mumps was written, it would look comparatively modern then, but relative to today's standards, mumps looks very archaic. But at the same time, it has some of the most intriguing and I would dare say novel features from the programming language archeology that certainly I've ever seen. And it's difficult to find some of these features anywhere else. And to see how it solved the business problems that it was originally designed for, I wanna kind of take a little trip back to about 50 years ago to 1966 when mumps was first made. So in 1966, just to sort of set your frame of reference, that's when NASA had just finished the first ever space docking. The U.S. Supreme Court had just decided on the Miranda case, which is the case that gave us the Miranda warnings and the Miranda rights. You have the right to remain silent, all of that stuff. And the Batman series starring Adam West had just debuted on television value. That's the pow biff Batman, not the Batman cartoon. So and here in New Orleans, the New Orleans Saints were created in the same year. It turns out that the NFL needed an antitrust exemption to merge with the AFL and the two most powerful senators who were blocking it were both from Louisiana. So they said, if you create the New Orleans Saints, if you give us a franchise here in New Orleans, we'll go ahead and vote for your deal. And so that's how you got the New Orleans Saints. But you don't care about any of that because you are a technology director for the Massachusetts General's University Hospital system. And for you, something much more important happened in 1966, which is that transistors, which had been around for a while. The first transistor was invented about 20 years before that in 1947, but transistors had started getting miniaturized and cheaper and they were starting to find their ways into sensors and small devices were capable of taking readings, for example, on patients. Now, doctors love these sensors because it means that the information that it used to take, say, a team of nurses to gather on a routine basis once an hour from a patient can now be done by effectively a robot, a set of wires and some recording instruments that just measure the values of those sensors. So doctors love this because it means each doctor can see many more patients per hour than they could before and they're able to provide better care. But there's a problem, which is how are you gonna implement that in a hospital your size? How are you gonna take all of these data points that are being measured once, multiple times a second, maybe multiple times a minute? And how are you gonna record this in a way that is suitable, that will be readable by humans later and accessible to practitioners that are providing care? How do you do that? And it turns out that it was almost like an IoT-like challenge, but in 1966 where these sensors were sort of distributed all over the place, they were really dumb. You couldn't do anything, they couldn't report their status, you could just write data and that was it. And you just have to make sure that you got the data that they were writing or else. Because if you miss even one or two of these or you record the wrong value, that might lead you to a false diagnosis or to prescribe the wrong treatment or to, in the worst case, not even notice that someone was having a problem at all and have some kind of serious consequences for patient health. So how do you get that really wide pipe of data into some system that's going to record that data? So that's the problem that Mumps was trying to solve in people's lives around the line. And they had some thoughts about how to do that. So in Mumps, it turns out that there is a built-in database. Now when you hear built-in database, what you might be thinking of is, like maybe there's a standard library function for talking to databases over like a primitive version of ODBC or some kind of specific communications protocol. But that's not what it's, you might be imagining maybe something like this where you have like, something that opens a connection and then something that does something on that connection. And then there's like some chunks that are provided for you by the standard library. That's not actually what I mean at all. What I mean is that the memory model for Mumps programs is not a continuously addressable chunk of linear memory the way it is in, I would say virtually every other modern execution environment. And it's actually a key value store. So that means that you read and write variables and memory by effectively addressing and querying them inside of this database. So essentially all Mumps programs had like a tiny version of MongoDB inside of them before 50 years, before MongoDB was cool. So it's like ultimate web scale, but for patients. So that's a bit hard maybe to wrap our heads around because we don't have any frame of reference for stuff that looks like that today. So what would that look like maybe in a pseudo version of Ruby that was trying to do the same thing? So a normal sort of assignment might look a non-Mumps type assignment, might look something like this. We have a couple of variables and you assign the right-hand side to the left-hand side, then you do some operations and if you want a more complex assignment you can take previous primitive assignments and put them together. With Mumps it's basically a very different version of that. So instead of regular assignments you get awesome extreme assignments and the way Mumps works is you are again setting keys and getting those keys later in this database. And you can perform operations on the keys to produce new keys or some set of values. That doesn't look appreciably different than assignment. That looks like it's maybe could almost be, like the equal sign could almost be syntax sugar for that. But what's different about Mumps is that you can do things like this. Show me all the variables that start with the letter S. Like that's somewhat harder to do in Ruby or other modern programming languages. It's hard to go into say C-sharper Java and say please enumerate all the variables in this program that start with the letter S. A lot of that information is lost at runtime or doesn't exist and so on. That's something you could do in Mumps. That's not actually Mumps. This is a Ruby-like version of Mumps. If I showed you Mumps it's basically unreadable. But that's a really interesting property because it's almost like a primitive version of metaprogramming. Remember this is 1966, right? Like metaprogramming, I don't even know if that was a word at that point in widespread use. So you can find these elements of your program almost dynamically which is really interesting. And that's a very different model than I think we're used to today. And so that's a really neat way to solve some of the problems that they were having because they basically just took all of this data and were dumping it directly to this database. There was no intermediate remote connection. The database was the program. It was one and the same thing. So that's actually kind of a really neat way to solve the program. If you need a database you put one inside of the same executing process that you're actually running the program in. So it's actually more like Mumps is a database that happens to have a programming language. So you don't need ActiveRecorder SQL or any of these ORMs. And so are there things that we can learn from Ruby's predecessors in the same way? Mumps did what it did because that was the best solution given the constraints of the time. I think we can say the same for every programming language that came after. The reason it was designed in the way that it was designed was because those were the constraints of the time. That was the context in which it was built. There were no frames of reference of things that came after that point. And so again, programming languages don't exist in isolation. They're as much cultural as they are technological. So Ruby's ancestors, if you go to the Ruby website or you sort of stare at Ruby long enough and adjacent to other programming languages, you'll see that it has a lot of similarities to aspects of these other programming languages. Now these are the five that are sort of explicitly stated as being historical ancestors and influences of Ruby. But all languages I think are inspired to some degree by this stuff that came before them. Everybody sort of stands on the shoulders of giants to some degree, especially in this field. And it could be helpful to kind of see the context here from Ruby's past. Lisp was basically the second oldest high level language in existence and widespread use only for Tran, which came one year before that is older. It's still in use. Smalltalk gave us this idea of dynamic objects whose not only their state can be mutated, but also their behavior can be added to or subtracted from at runtime. So if you've ever opened up a object in Ruby and added more methods to it, you are basically doing what Smalltalk did in 1969. Ada, Eiffel, Pearl, all of these sort of added more structural ideas, structural elements to the way that we design and build our programs. And then Ruby got to benefit in some sense from all of the work that had been done before. So those are its predecessors. What about Ruby's contemporary? So things that are roughly around the same time as Ruby were the JVM-based languages of Java initially, then later on followed by more modern successors like Scala and Kotlin, Clojure, et cetera. And then right around slightly before, Ruby were common Lisp, which was a standardization of Lisp. Remember, Lisp had been around for a really long time. There were like 300 different versions of it. Everybody got together in a room and said let's make one kind of version we all agree on. I mean Haskell has some of the same ideas as well. What about the things that came after Ruby? So there are things like Elixir, Swift, and Elm, languages that actually took some inspiration from parts of Ruby or their developers, or other their first few developers were often Rubyists to begin with. And so it was a really broad ecosystem of sort of before, during, and after that Ruby influenced or was influenced by. And some of the things I wanna talk about today are what are the aspects that those languages have that might be interesting to bring back to Ruby? And I'm gonna divide that into a few different segments. So if you can imagine a landscape of possible choices we could pick from to add to Ruby, where the things, this axis on the left represents things that are better for Ruby in some subjective measurement. So more payoff for doing these things. And then on the bottom axis, the left to right one, we have things that require more work. So stuff that's harder to put into Ruby, something like gradual typing might be a lot of work relative to something like changing a few error messages. So you can think of a line of possibilities where you have a pretty even trade-off between the amount of work you have to do and the payoff that you get for doing that work. Everything that's on the line is a pretty fair trade-off. Stuff that's above the line is more a payoff for less work, and stuff that's below the line is more work for not as much payoff. So we wanna try to find the things that are above the line and hopefully to the left, as to the left as possible. And we'll start off with some things that I think are pretty easy, at least based on my sort of very simplistic implementation of them in the variant Ruby in the GitHub repository that I'll show you. The first thing we need to take a look at is error messages. So if you're a beginner in any programming language, what's the first thing you see? It's probably the REPL or the first Hello World program that you wrote or whatever your interface to the programming language is. But the second thing you see is when you screw that programming up and then you get an error message. And then your first negative experience will be defined by how helpful that error message is as fixing or finding the problem for you. So let's start with C++, for example. This is a very simple but incorrect C++ program. It is not syntactically valid. The type of this variable needs to be defined and it also doesn't declare a main or anything like that. So if you had to guess, this is a line which will compile this program and then send the standard error, which is where errors would normally go from a compiler to standard out. And then we're gonna count the number of error lines that are generated by trying to compile this program. So how many errors would you guess that there are? So just order of magnitude. Raise your hand if you think the number of errors is between zero and 10. As you like, this is obviously a trick question, it wouldn't be that easy. Raise your hand if you think it's between 10 and 100. How about 100 and 1,000? How about 1,000 to 10,000? How about 10,000 to 100,000? All right, you are not nearly pessimistic enough. So I know you're not gonna believe me, so we're just gonna pop over to the demo here. So here's that same file just to prove it was gonna nothing up my sleeve, right? There's the same file that we saw before and now let's try to compile it. Well, I'll show you what the errors are first so you can, oops, well, that's obviously an error because I didn't type it correctly. There's a lot of stuff about the type not being named if anybody gets confused because it thinks that this is basically a template instantiation and it wants to, it wants you to, okay, so there you go, 20,702 lines. All right, this one says to 20,701 lines, so maybe I had an extra no by there or something. So a lot of lines and it all looks like that. So it all looks like this. It's the same error of 5,000 times. So now that's a pretty unreasonable, like that's cheating, right? Like probably your first program's not gonna look like that unless you made a horrible typo and then you'd be like, obviously that's not correct or it shouldn't look like that. So it's cheating a little bit, but I think it illustrates the point that that's not a great user experience to show you the same error 5,000 times. So in Ruby, a really simple program, A, B, two arguments to this add function and we're gonna add them together and return the value. All right, so if you do something, and forgetting about the function itself for a second, if you do this, so if you add the string one plus the integer two, you'll get told that there is a type error, right? There's no implicit conversion of integer into string. So that means that this value is, this is really calling a plus method that's on string and expecting that there is a string on the other side of that plus and there isn't, it's an integer and there's no implicit conversion of integer into string. So the first piece of this error message is, hey, here's the problem. This is the thing that you need to fix. Now, if you change the order of the arguments, if you change it to one plus the string two, you get a different error message, which is string can't be coerced into integer. So, okay, that's good that we're being told what the problem is and it seems relatively clear, but I wanna compare that to Elm where a similar function that I define bit is an add function that takes two integer arguments. I get a really nice message that says, hey, type mismatch, the second argument to this function named add is causing a mismatch. Here's the line that caused the mismatch. Here highlighted in red is the thing that is the problem that you need to fix and here's what I was expecting to happen, but here's what actually happened. So, spraking that down again, there's a piece that says, here's the problem that you need to fix. There's a piece that says, here's the context for that problem. There's a piece that says, here's the, here's what you have to do. Here's what, here's the mismatch in my expectations that you need to resolve. So, it was integer or it's supposed to be integer, but I received a string. Now, when you go to Ruby and you look at real problems, so here's an actual stack trace from a project that I'm working on. This is from Honey Badger, which is a SaaS service that will take your exceptions and provide you a nice wrapper around them. And that's often necessary because you don't wanna have like monitor the exceptions yourself and some production system so you offload that to somebody else's responsibility and you pay the money, which is what we do for Honey Badger. So, this line caused a no method error because the method map is not defined for no values. So, this is what Honey Badger is telling me, but what Ruby told me was just really a stack trace. All Ruby told me was this and oh yeah, there's a problem on line 46 of this file. Now, what would be great is if Ruby told me, hey, this value, consolidated history, that's nil, that's the nil thing. This is the line that is the problem. I'm gonna print it out for you and I'll highlight the part of the value that's the problem, which is this consolidated history part. What would be awesome is to see how the consolidated history get to be nil. It's obviously defined somewhere else on this block because it's not a name error, it's just a nil error, it's a name error, so if consolidated history wasn't defined anywhere, I would get a different error, but it is defined somewhere else, so I'd like to see why was it nil, how did this happen? So, there's context that could be added to that message that would be helpful for me. So, a suggestion would be a way to improve these error messages would be, what if we could clearly identify these salient values that are participating in the error that's just been raised? If we could state the difference between expectations and reality, we'd have a lot clearer picture of what we need to fix. We could provide context and related values for the problem and then we can tell the story of the problem by putting all those pieces together. So, right, so exception handling. So, Ruby has this idea of sort of bipartite exception handling of two chunks. You've got the part where you have a thing that you want to happen and then there's a part where that thing goes wrong, let's branch into this other block of code to try to solve the problem and that's bipartite exception handling, two parts. Lisp and some other kinds of similar languages, Haskell among them has a different concept of tripartite handling, what they call condition. So, you have a, rather than having a, this is what happens when everything goes right and this is what happens in branch to one or more of these blocks when something goes wrong, that's the sort of Ruby, Java, C sharp, OOP, more flavored approach. Functional programming is sort of a different take on that which is condition. So, you have a thing that you'd like to be true. I mean, if that invariant is violated at some point, so it's, if that invariant is no longer true, then we're going to compensate for that by branching into this piece of code and then we're going to do something based on that that preserves the invariant or tries to preserve the invariant. I mean, at that point, you can actually go back to the state where the invariant was broken because now the invariant's true again. So, if, so in other words, it gives you a chance to kind of repair the situation in a way that's different from like, began rescue and then like finally or recover or those kinds of blocks. That was the first thing. The next thing I was going to talk about was pattern matching. So, in Ruby, you have case statements, right? You have, you have a switch in case as a construct is pretty popular, not just in Ruby but in many other languages. And the general idea is that you have like an object or a condition and you're saying, okay, given this object, what would, what are some of the possible values or states that it could have and then I'd like to take a different action depending on whether it's in one or more of those states. Well, with pattern matching, it's a much more structural approach rather than you having to define the individual states and say, well, this, you know, if it's this then do that, if it's this then do that rather than having effectively what's a series of if then blocks, you instead have an object and then you're required to enumerate all of the possibilities that that object could take on. So it, you're guaranteed that no state is, no stones left unturned effectively. So you can't have an error where you've forgotten about a state. So it's almost like a form of static type checking but doesn't require having a static type system to do that. Just requires you being able to say, here's all the cases and if I don't handle anything, I, if I don't handle anything, I want, I want that to be an error. I want you to tell me. So this is to be like, if you know that the value is an integer, you say, I expect this to be an integer. Then immediately, if it's not an integer, you get an error and then you'd be required to handle every integer case. So if there's a possible value that that integer could take on but is not accounted for in the statement that would also be an error. So you can get more complex than just the values though because you could talk about the relationships that the types have to each other. So for example, if a, if you have an object that is say, some kind of person, I hate to use the person hierarchy, but let's just say it's some kind of person and let's say it's some kind of profession. So, and a profession could be in this type hierarchy, either a doctor, a lawyer, a nurse, or a sports ball player. So if you're, when I get an object of type profession, my pattern matching can tell me, okay, I know that this is an object of that kind and therefore it must be one of these four subtypes or the original higher level type. And then I'm going to tell you about, and then I'm going to tell you what to do in each of those four cases. And if I haven't declared or described one or more of those cases, that's an error. Right now, if you can't really do that, there's nobody kind of getting your back, certainly not the interpreters, not getting your back in terms of, oh yeah, I forgot that or I left that out. So this would be a way of introspecting the combination of a type hierarchy plus the values that are being defined in the suite that are provided to the switch statement and then branching to one or more of the cases as a result. So that's super popular and very useful, I would say, and anytime you're working with a DSL, which is very, very aligned to what I think a lot of people would say ruby strengths are the utility of having pattern matching at your disposal is so high that it's a pretty common feature even in relatively newer languages like Swift. It's like Elixir has this, Swift has this, Erlang has this, Haskell has this. So it's pretty popular in the functional approach. All right, so next is the idea of explicit module shadowing. So this is where you have an object that mixes in some modules. So this is a ruby class and it's mixing in like say module A and module B. Now you need to override a method that is called in one of those modules. And later, so what you do is you say, okay, I know that that module definition is out there in the original module file, but I need a different definition. I need that method to behave differently. This is super common in Rails, this happens a lot where you include some other module and it gets included into your class that you own but you don't own the module. And then you want some behavior to happen and the way that the owners of the module allow you to change that behavior is for you to define your own methods. So the problem is that later, when someone's reading the class that you wrote that shadows that module method, they have no idea where that method came from or why it's useful. You just have this method that's called like Foo and they don't understand that Foo has anything to do with the module. It's just a method that's floating around in the class that happens to be later called by the module. So there's no explicit connection there. So a really simple thing to do which I guess I'll send the slides afterwards and maybe we record this thing. And I'll show you the GitHub repo but a really straightforward thing you can do if you want to mutate your own local copy of Ruby is you can require that classes, sorry, that methods explicitly state what, when they're overriding that they're doing that. So for example, you can make it an error. In the example I have, you can make it an error to override a method that's already defined without annotating it in some way, without providing a sort of a special marker which says, hey, hold on, just so you know, if you're reading this later, I overwrote this method. This method actually already exists in the method lookup chain for this particular object and I'm replacing its method to lookup with the one that's here instead. So that's called explicit module shadowing in the case of a module. So this is required, it's much more likely to be seen in languages that give you a really strong type hierarchy, a very opinionated type hierarchy like Java or C sharp where there's very strong likelihood that you're going to override behavior in an explicit way and they want you to be super, super obvious that you're doing that. So Java has like this override annotation. I don't think an annotation is really the right way to do that but doesn't look very Ruby-ish to see like annotations all over your code but I think it is kind of Ruby-ish to put like def, this is from some module dot, the name of the method you're actually defining because you can already do that. So this is just requiring you to be explicit when you do do that so there's no confusion or no possibility of confusion. Another thing I was going to talk about was type inferencing. So this is not a new idea and implementations of it are cropping up more and more in different languages but type inferencing as an idea that's been around in academic programming language circles has been around since the 1970s and it's basically this idea that you like types but you also don't want to rewrite everything that you've already written just for the sake of getting types and at the same time you don't want to have to say what type everything is so you don't want to have to, let's say imagine a future version of Ruby that has something like gradual typing in it or maybe even like static typing where you're required to describe exactly what type each value that you're assigning to is in a future version of Ruby that looks like that all of your code is now broken because you'd have to say what type everything is all of a sudden and that's not fun or at least it's not as fun as almost everything else. So the way around that and one way around that is to use what's called type inferencing where if the compiler or interpreter or whatever the execution engine for your programming languages if it can determine that type of something then it should use that information in whatever way it can. So if for example Ruby knows that you've just done i equals 42 then the compiler knows for sure that i for everything after that point until it's assigned to you at some later point is an integer or is a number and if that's true then it can also make assumptions about everything that i touches and see if it also is going to be problematic in that way. So that provides the sort of bedrock that's necessary to do a lot more powerful things like maybe gradual typing but without requiring you to do a whole lot of work it's just the compiler taking advantage of what's already there and saying let me add a lot of metadata to what I know about what you've written and then say what would that metadata tell me about the future state of your program and how can I help you understand more about the program? So this syncs really well with a lot of the stuff we talked about previously with the error messages like imagine Ruby being able to tell you oh yeah that thing that you thought was a person you actually passed me like a rabbit so I don't know what to do with that even though they have a similar kind of type signature and a similar duck typing it's not really the same objects even though I was like halfway able to invoke some of the same methods on it without blowing up that probably isn't what you meant to give me you probably meant to give me a person so those were the four big ones that I can remember off the top of my head but the idea is that I think all four of those ideas and again I'll talk to Abby and get everyone on the slides later but all four of those ideas combined with kind of a healthy appreciation for the fact that other languages can have something to teach Ruby and that the predecessors of Ruby also can still have something to teach Ruby I think we'll go a long way to ensuring that Ruby is as vibrant and as exciting and fun to work with as it is today and has been for a really long time so that's everything I had to say thank you sorry about the technical difficulties and I'll get a new laptop all right let me see if there are any more Twitter questions okay all right is the mic still on yes okay Keith Walsh asked per type inference have you checked out crystal oh not on tests yeah okay Keith Walsh asked have you checked out crystal language so crystal is basically it looks exactly like Ruby not exactly it looks almost exactly like Ruby but it's strongly typed and it has a lot of really interesting features that I think Rubyist would be attracted to if you like static typing but you're like I also like the way Ruby looks and feels when I write it crystal is extremely appealing in that respect I think I don't think it's for everyone like for example none of the libraries that are Ruby libraries work in crystal it's not a they're not binary compatible we don't one-to-one map or anything so you'd have to rewrite everything you already had but it's a really cool alternative to it's a really similar Ruby flavor in the same way that like Elixir is you know sort of similar to Ruby and clearly inspired by it in many respects and if you know if you know anything about the creator he's been very heavily affiliated with Rails and done a lot of work there and devise so those those sort of different facets of Ruby I think really reflect that like Ruby at its core is really awesome and interesting and that people are just kind of taking that awesome and interesting centerpiece and saying what can I kind of add to or modify to make this even more awesome and interesting so yes Keith I have heard of crystal I think it's really cool have not used it in production don't think you I don't know of anyone who currently is but I expect that maybe to change as the ecosystem grows other questions I feel like another question about the font yeah again the font is Fanta Skew's Sans Mono I love I hope your favorite part of this wasn't the font that I use but thank you so I didn't see any other big questions on Twitter I'll follow up with folks afterwards thank you again for sitting through the technical difficulties and have a wonderful rest of RubyConf