RubyConf AU 2017
You And Your Type[s] Aren't Welcome Here

http://www.rubyconf.org.au

Whenever the stereotypical Ruby developer hears the words "statically-typed", they have one of two responses:

they have flash-backs to their C++/Java days they prepare their memorized Alan Kay / Kent Beck quotes for battle. Now that stronger typing is coming to Ruby, maybe we need to calm down a little.

This talk is not designed to persuade anyone to drop our beloved Ruby, but maybe we can learn some lessons from our stronger-typed friends to bring even greater happiness than before.

RubyConf AU 2017

00:00:08.160 So many people gasping at RubyConf! I would like to start us off this afternoon with a few self-evident truths. I don't want you to think at all for the first part of this talk; I want you to respond with pure emotion — all the rage or the joy you can muster. Pure lizard brain! Let's start with a nice easy one. Alright, what about this?
00:00:19.020 Hey, now you're warming up! Okay, let's knock it up a notch or two. Excellent! There we go! Alright, what about 'dear'? Yeah, fairly confident on that one. And last but not least, programmers love having strong opinions on topics. Before we move on, I would like to just say that all of those opinions were completely wrong.
00:00:43.509 My name is Andy Nicholson. I work at Cogent, and by the way, breaking news: the Supreme Court has upheld the decision to stay the immigration ban. So that's pretty awesome, I think! But enough of that. We are also hiring, and all I will say about that is I don’t believe that I will ever work at a better place in my entire career. It’s a big deal! If you want to know why, find me or my colleagues after any of the breaks, and we’ll be happy to talk to you. I’m Andy Nicholson unless the Twitter things and all flames can be seen to differ.
00:01:29.760 So today we're going to talk about something quite controversial in the Ruby world, and that is types. Some of you might have heard the talk description and thought, 'No way! No way! I came to Ruby to get away from that noise! I never wanted to write public static void main again!' Or you might have reached for your favorite Alan Kay quote and told me that he didn't have sleep in mind when he conceptualized object orientation. If that was your reaction when you read the talk description, relax. Look at the others. It’s going to be okay.
00:02:03.218 I'm not here to tell you that I've seen the light and that statically typing all the things is what you should do in all your programs. I'm not here to tell you that method missing is considered harmful or that Ruby is a blight. I'm not even here to tell you that objects are a poor person’s disclosure. If you're new to Ruby and none of that made any sense at all, it’s okay! Welcome.
00:02:38.210 Don't worry, software development can be a pretty tribal industry, but it doesn’t have to be, and you can be part of the solution. A bit like them! If we're going to make any headway this afternoon about types, we’ll need to talk about them in a way that gives off more light than heat. We're going to need some definitions, so let's start with the font of all wisdom — Wikipedia.
00:03:26.360 Wikipedia says a type system is a set of rules that assign a property called 'type' to various constructs a computer program consists of, such as variables, expressions, functions, or modules. The main purpose of a type system is to reduce possibilities for bugs in computer programs by defining interfaces between different parts of a computer program and then checking that the parts are being connected in a consistent way.
00:04:40.530 That's a long definition! For the purposes of this talk, a type as a piece of metadata encodes something we know about our code or the data it operates on into the code itself or the language. If any high schoolers are worried that this definition precludes types being data, you may be at the wrong conference. We can use types to describe how much memory should be allocated for a piece of data.
00:05:06.400 We can use them to describe how to interpret or encode the data that we have, whether we're allowed to change it or not, or even whether, as in Aaron’s talk, we actually have the value itself or just addressing the value. Now, all of those examples will sound familiar if you've written code in C, C++, PHP, Java, or even Rust. If you came to Ruby from a language like Java, you probably felt, as I did, an initial surge of freedom as boilerplate you hated writing just seemed to melt away.
00:05:41.620 Then, a little bit later, you might have faced confusion over an error message that didn't make much sense. How could we do better than that without static types? We will come back to that later.
00:06:11.500 Ruby does have types; they might just be a little bit harder to spot. I had to put an emoji in because emojis are one of the most common statements we like to use to describe Ruby. Everything is an object. Now, that statement is itself a simple, simplistic type declaration. It means that every value can be asked questions and sent messages. It may not feel like a type declaration, but it matches our definition; it's just baked into the soul of Ruby, not a syntax.
00:06:40.300 Okay, apart from explaining how to time to do work, it doesn't appear on the surface as useful. Well, we also make type declarations every time we make an ethical decision when we call a method. When we call a method, we are asserting to the Ruby compiler that the object has the type it is expected to respond to. We just don’t do anything to enforce it, and let it crash as well if it's wrong.
00:06:57.660 And that’s what we mean when we talk about duck typing. If an object looks like a duck, swims like a duck, and sounds like a duck, it’s probably a duck. We can restate that in our typing language: if a method responds to the message we expected it to, it’s probably the right type. Most of the time, this works pretty well. As developers actively working on a system, we keep a large amount of knowledge in our heads, so we're often able to remember what the correct methods and the types implicit should be.
00:07:44.060 Of course, there are trade-offs. Because of the way Ruby works, we can't know if the code is actually correct until we run it. Actually running and executing our code is the only way we can be confident that we've captured our thoughts correctly and accurately. This is one of the reasons rubists bang on so much about testing, because without testing, we're flying blind. Yet, still a lot, maybe even most of the time, this works quite well.
00:08:14.500 As we use TDD and let our tests define the design blueprint for our code’s behavior, we end up with nice, flexible, well-thought-out code that’s easy to change. Sometimes, consider this code: I like Marcos’s definition of 'sliding' towards the last clean day. We have a few methods here.
00:09:06.810 We have a print_items method that takes a collection and goes through that collection. For each item, we convert it to a string and print it to the console. We have another method called process_report that takes some data and calls print_items on that data. What happens when we do this? It explodes! We get an error from Ruby saying that there was an undefined method 'upcase' for nil.
00:09:42.170 Okay, that’s not so bad. We realize that the item was expected to have a code, and it didn't, so it got returned as nil, and there’s no 'type' case method on nil. So, we've got an error. We’ll make a small change to use the fetch method instead, and now if we make the same mistake, we get a much better error. We find that there's no key called 'code' in that particular hash.
00:09:56.480 Okay, great! So, a little bit later in our project, we make another mistake and we call this process_report method with an empty array. One ray of irate when we get this: 'no implicit conversion of symbol into integer.' Well, this is where duck typing has unfortunately led us down a path. We passed an array, which responds to the fetch method the same way a hash does, but they don't do the same thing. It wasn't a hash; it was an array.
00:10:34.260 Turns out the array fetch is looking to find an entry by an index, not by a key, and so it tries to coerce whatever you pass it into an integer. You can't coerce a symbol into an integer! Explosions! If you've ever had to debug the square bracket access on an array when the code thought it was sending a message to a hash, you've seen that error before.
00:11:08.950 So, let’s make another small change. Before we do anything with the items in the collection, let’s just turn them into a hash using the hash collision operator that Ruby provides. Now, if we make a bunch of mistakes, we get sane errors for them. I don't know if you can see them through the contrast. If we pass in a collection of an empty array, we get the 'key not found' error. This is the same for an empty hash.
00:12:04.700 The reason they are the same in this case is that an empty array coerces into a hash. But if we pass something in that completely doesn't make sense, we actually get a useful error: 'you can't convert an array like that into a hash.' The benefit of using that hash coercion operator feels like someone leaked out the information in this talk yesterday.
00:12:34.400 Touch them as briefly mentioned that anything that knows how to be coerced into a hash can be part of this method, and it will work. We had a plant class that just has two hashes defined on it; we can pass that into our process_report method, and that will work just fine.
00:13:14.100 If you’re curious, we add any of this sort of thing up the agreement. His excellent book, 'Confident Ruby,' spends a fair bit of time talking about the explicit and implicit ways data can be coerced in Ruby, and I found it a particularly great way to gain more confidence with the data moving in and out of my methods.
00:13:56.700 You can also look at Contracts Ruby, which has been around for quite a while — a way of implementing type signatures of a sort in front of methods. It’s also the dry types method from dry-rb we’ve heard a bit about already. What are the things we could think about from a typing perspective?
00:14:38.340 What about code execution? So you’ve got some Ruby code that needs to do a bunch of operations in a certain order, and if there's a breakage, you want to stop, handle the errors properly, and return some result. You’ve probably written code like this.
00:15:45.680 No, I have! And not just for this slide! So, this doesn’t handle errors at all. So we might say, 'Okay! I need to handle errors in my code.' And so I’ll do this: I have a begin-rescue-end block and I’ll rescue from all my errors. But then if any of these operations might return the same error, we might need to capture the result from each of them sequentially. That doesn't look very good. It’s got to be something better than that.
00:16:01.710 What if instead our code could look like this? The code now looks much more like we want it to read. I’m going to do an operation that might fail and then another one and then another one in that order. If the chain of operations succeeded, then we can return the value from that. If something blew up, we can raise that exception or log it or whatever. And if this looks a little bit like the result type that Rahul showed us in the talk just before, that's not an accident.
00:17:06.500 This is actually an implementation of basically that exact type from the dry monads gem which is an unfortunate name. If you are anything like me, you see one adds in a gem name and you run a mile! But it doesn’t look like there are any types here, does it? Apart from that weird writing at the beginning. But let’s break down what it’s actually doing.
00:17:52.040 Let's try operate: at the beginning it takes a block. That block either succeeds, meaning it doesn't raise an exception, or it fails. If it succeeds, its result is wrapped in a success object, which is then passed into the next bind method in the next block, and so on, all the way down the chain. If it fails, the failure is wrapped up in a failure object, and that failure object is also passed down the chain. But success and failure objects respond to bind; they just do different things with it.
00:18:34.610 So if the operation succeeded and you get a success object and you call bind on the success, you can pass another block in. The result of that previous operation gets passed down as the result in that block, and on you go. If you get a failure and you call bind on it, your signal is to throw the result away and return itself. I presume it should do that, and that’s safe.
00:19:55.130 You don’t need to worry about anything going wrong in between or ending up with something half-baked, and you can catch the error that happened and do something with it. I’m pretty new to this way of thinking; I’m not an expert. I’m very new to type languages. I’ve looked at a half score book once; I have a couple of OCaml books on my desk, and I’ve enjoyed dabbling with Elm quite a lot.
00:20:58.420 But I found that thinking about types in this way helps me think more clearly about what I’m actually trying to achieve, and it didn’t ruin my Ruby code at all. I didn’t magically turn my Ruby code into Java. I didn’t necessarily even need to change the way that the code looked; I just needed to think in a slightly different way.
00:21:34.570 So, you could use this kind of type thinking in a bunch of different ways. I haven’t tried them yet, but you could use this to model state machines or other series of asynchronous operations — a whole bunch of different things. I’d love to talk to you more about ideas that you might have on how you could use types in your code.
00:21:54.550 Intuitively, we already get that there are different ways to do things and that ideas can flow backwards and forwards between paradigms and languages. We know that MRI, the standard C implementation of Ruby, has drawbacks when it comes to concurrency. So, we’ve taken the developer experience of Ruby’s pleasant syntax to the Erlang VM and given the world Elixir. You might have heard of it!
00:22:36.290 But as Marcus so vividly showed us yesterday, we don't have to do that. There are ways to use concurrency models like actors in Ruby, and there have been for some time! It's not either/or. You don't have to throw Ruby away just to get something that it might be not so good at.
00:23:11.650 Again, more recently, some smart developers from Argentina wondered what Ruby would look like if it completely embraced a type system and compiled. And so now we have the Crystal language as well! Crystal is a worthy language if you're prepared to lose some of the dynamism that makes Ruby so fun to use.
00:23:49.260 But being more forceful about types in your code doesn’t have to mean sacrificing elegance. Rails showed the world how easy it could be to get up and running on a full stack web application and the benefits of good tooling. Sprockets, for its time, was a fantastic piece of software for the asset pipeline and it inspired the Ruby community to support a truly dizzying amount of front-end technology.
00:24:28.670 Including home reading it as a language, and as a community, has always embraced diversity of expression. We disagree with the Pythonistas when they say there should be one, and preferably only one, obvious way to do it.
00:25:07.930 So, I wrote a long blog post, and came up with this beautiful statement: Ruby really took disparate elements from Perl, Python, Smalltalk, and combined them in a harmonious blend. This artful blend of disparate paradigms is not a fluke; it’s just the most recent step in a rich tradition stretching back at least a thousand years.
00:25:49.290 Hopefully, some of the other talks here have inspired you to take a look at some languages like Elixir, Crystal, or Elm. Have a chat with a random attendee about Elixir; they've probably tried it! Talk to Louis, or Rahul or Kevin, and Marcus at 47 about Elm. Talk to myself about Crystal.
00:26:17.300 But as you do, be asking yourself: how can thinking about types make my Ruby code more fun, more structured, more stable, and more of an adventure? Thank you very much!