Episode 2: This Increasingly Complicated World
James Edward Gray II doubles down on Twitter arguments about static type systems, modern software tooling, the mind-expanding effects of being a polyglot programmer, and the design possibilities of massively concurrent systems.
- Download an MP3
- Subscribe via RSS or iTunes
- Featuring @JEG2 and @tomstuart
- Music by Martin Austwick (@martinaustwick)
- Published on 2016-07-29 at 16:00 UTC
- Recorded on 2016-07-20
- Contact @whyarecomputers or email@example.com
Tom: Hi, welcome to Why Are Computers, a podcast that’s definitely got something to do with computers.
I’m Tom Stuart. This is episode two, hot on the heels of episode one.
I’m joined today by James Edward Gray II. Hi James!
James: Hello Tom!
Tom: Who are you?
James: I have been involved in the Ruby community for a long time. If anybody knows me, it’s probably from there. Although in more recent years — or, I guess I should say, this year — I’ve been spending a lot of time in the Elixir community.
Tom: Right. So, the reason we’re having this conversation is because you decided to start some kind of Twitter beef with me, and that somehow escalated into a voice conversation. I was a guest on the Ruby Rogues podcast, which you are no longer co-host of, although you were up until quite recently, I think.
James: Yeah, I did that for like three years.
Tom: Yeah. Well I was on that recently, and I said all of my correct opinions about things, and it was, you know, it was largely well-received. And really the only fly in the ointment was you complaining on Twitter, saying that I’d got everything wrong.
James: Right, right.
Tom: And for a while I thought it had… you know, you did a couple of tweets and then you just shut up.
Tom: And that was… good.
James: Yeah. Went away.
Tom: But then, about a month later, you unleashed this gigantic tweetstorm at me: four separate complaints divided over twenty-three separate tweets.
Tom: So I guess we need to settle this in some kind of verbal deathmatch.
James: Yeah. I think the problem was that I was just sitting there listening — you know, since I’ve left the show I still listen to the episodes —
James: — and it was a really good episode, and I got super into it, and then I found myself arguing with you out loud while you were talking, which is very not interesting, because you don’t argue back.
James: So I decided that I needed to discuss this with you. Because, basically, you know, somebody was wrong on the internet. And it was you.
Tom: Right. Well, it had to happen one day.
Tom: Well, so let’s get into it. There were four basic things that you said on Twitter, that you felt like you disagreed with.
Tom: Shall we just go through those? And we can see who can win each of those arguments.
James: Each of those rounds? Yeah, sure.
Tom: Round one: you had a bunch of points. In the episode of Ruby Rogues, I said a few things about static typing, because one of the hosts, Sam, had recently watched a talk that I gave at RubyConf Australia 2015, where I talked about static typing. And he wanted to talk about that a bit, so we talked a bit about static typing. But then you came back on Twitter saying you felt like the static typing discussion undersold the value of static typing a bit. Would that be correct?
James: Yeah, yeah. I think that’s good. And I should say, like, I pretty much agreed with almost everything you said about static typing. You talked about what’s happening in Ruby — if some kind of type system’s going to be introduced, and what level of value that could have for a language like Ruby, and I totally agreed with that analysis…
James: …but I felt like the discussion overall was like, “enh, static typing, it’s okay, but it’s kind of a pain”, and that was the part where I was like, I’m not sure I agree with that.
Tom: So how do you feel?
James: Well, there’s lots of different kinds of static typing, so I think we should definitely clarify. You know, you have things like Java, which are famous for a lot of hassle with very low benefit. And then you have things like Haskell or maybe Idris with very impressive benefit and modelling capabilities, as long as you have a PhD or something.
James: So those are extremes, I think, on the scale. But there’s a lot of things in the middle. You brought up Rust, which I think is a really good example of a static typing system that’s not that tough to deal with, and you get some kind of amazing benefits out of it.
Gary Bernhardt had this talk at Strange Loop where he talked about the different ways we’re trying to validate that a program does what we think it does, or that kind of thing. And he talked about static typing just reducing the set of things that you have to worry about. As programmers we have to worry about a lot of things, and increasingly more things over time. In the past, especially if you were a web app developer, I don’t think we had to spend that much time thinking about concurrency.
But now computers have kind of hit the wall, and the only way we’re going forward is concurrency, you know. And then you have to worry about: is it safe for me to access this thing if somebody else might be accessing this thing?, and stuff like that. And Rust can answer that question for you! If you try to set some variable to a number and then you spawn a bunch of separate process thread things that add to the variable, Rust will just fail to compile your code, and it will say, “that’s not safe, you shouldn’t do that”. And I think that’s some of the value in them, is that they can help us keep track of what’s going on in this increasingly complicated world.
Tom: Right. I would agree with that. In the talk that I gave I talked about this quite a lot. That talk was essentially advocating for static type systems, and saying that they were a thing that we should seriously consider. I mean, I guess my concerns were really to do with, not so much “are static type systems a good idea?”, because I definitely believe that they are a good idea — and as I said at the time, I think, the whole idea of taking information out of the brain of the programmer and writing it down in the program is a really good one. The more we can express our intentions and what our expectations of the invariants of the code are and stuff, the better the computer is able to understand what we mean, and to check those things, and make all those compile-time checks that you mention there.
I suppose my concern is really more about retrofitting that onto a language like Ruby: how is it possible to take those ideas and import them into Ruby? So I suppose what I’m interested in hearing from you is, given all of that stuff that you’ve said about Rust, and you also mentioned Elm when you were tweeting at me — it sounds like you’ve had good experiences with Elm’s type system — do you feel like there’s stuff in those languages that could be straightforwardly applied to Ruby, and it’s just a question of engineering, or do you think that there’s a more fundamental difficulty there?
James: Yeah, so that’s a good question. Elm is pretty neat as far as its type system goes. I had a really big “aha!” moment in working with Elm, where I built something and I took it to the Elm community; I’m like, “how is this?”, and they’re like, “enh, it’s okay, but you could do better!”. And one of the things they taught me is that, when you’re programming Elm, you can try and arrange the types so that impossible states never happen — so that you just can’t have them. And that would mean, if you try to program something that would lead to an impossible state, your code just doesn’t compile.
And when you get the hang of that it’s kind of amazing to realise that once your software compiles you have a really high level of confidence in how well it works, way higher than I do when my Ruby program compiles. My Ruby program can compile and be gobbledygook. But in Elm you can actually make it where you have a high level of confidence with that. And that has value also when you go to change things. Because the compiler is so good in that way, and the type system so strong, that you can make the change, run it, and literally follow all the errors, and when you’ve addressed those then the change works. It’s been propagated through the system and you can be confident that you didn’t miss that particular case.
Another, I think, real plus of Elm’s system — and Rust is getting there — are the good error messages. It was actually funny; on the Ruby Rogues episode, the panel and you talked a little bit about the famously bad error messages of compilers and type checking, which is definitely true historically. But nowadays, I think it’s actually getting where sometimes you can get a message and it’s like, “you did this wrong, but I think you mean this, you should try this, and here’s some documentation to read about what you’re doing and stuff”. And that’s kind of amazing — the compiler being able to help you to get to the right solution because it can tell what you’re trying to accomplish through types and stuff.
So that was a, I guess, aside on why I think Elm and such are great. But to answer your actual question of, do I think this can be done in Ruby? My initial instinct is much like yours, that it would be very hard given how Ruby is. Although that said, I have spent a lot of time in the Elixir community lately, and Elixir is not as dynamic as Ruby — it’s dynamic but not as much as Ruby — but it does have things like
nil and stuff that you don’t see in a lot of the stronger static typing systems, and dynamic types and those kinds of things. And it has an add-on tool, really through Erlang, called Dialyzer.
James: And that tool does a system called “success typing”, which was a new concept to me as I started to get into all of this. And it goes through and, I guess, tries to determine what possible things could lead to success in the types. And it’s all done through static analysis and add-on stuff — there’s no support directly in the language syntax for it and everything. And I must say, in using it a little bit, I’m super impressed with the kinds of things that it can catch sometimes. So maybe it’s not as impossible as we think. But I tend to agree with you that it seems unlikely to get big wins in a language like Ruby.
Tom: Okay, that’s interesting. Well, maybe we kind of agree on this then. It might not be that much of a point of contention. Whenever I think about type systems, I’ve always got this picture in my head of the fact that we can’t possibly statically discover everything that we want to know about our programs. Right? Our static analysis is always going to be constrained by the fact that we’re writing programs in a Turing-complete language, and there are always going to be properties of that program that can’t be easily — or perhaps ever — decided just by looking at the program statically. The only way you’re going to be able to understand or see what happens when you run the program is to actually run it.
So static type systems are always about taking an approximation to correctness; to having some kind of trade-off between freedom and safety. You’re going to set the slider somewhere and say, “well, I want the type system to give me confidence about certain properties of the program, so I’m only going to allow the programmer to write programs that the type system can see are definitely safe”. And if there’s ever any ambiguity about whether a program is safe or not, then the type system is going to come down on the side of saying that it’s not safe. And that conservatism means that you’re always constraining the set of programs that you can write.
It’s this thing that Simon Peyton Jones calls “the Region of Abysmal Pain”: that disconnect between the programs that you want to write — all of the meaningful programs — and all of the programs that the type system will accept. On a Venn diagram there’s an overlap, which is the good bit, but then there’s this region outside of the overlap, which is all of those programs you wish you could write, but your type system doesn’t let you. And I suppose my worry is that with languages that have historically been very dynamic, and that haven’t grown up with a static type system, it feels very difficult to take those languages and say “we’re going to add this feature to the language that means that now a bunch of programs that used to be valid programs aren’t valid programs any more”.
I mean, best case scenario, you would come up with a static type system that somehow managed to check all of the programs you’d ever written. So you could take the whole codebase of Ruby on Rails, for example, and just run it on a hypothetical Ruby 3.0 that’s got a static type system, and it would all work. The Ruby interpreter would infer the correct type for every identifier in the program, and it would say “yep, that whole Rails source code typechecks and everything’s fine”, but that just seems like science fiction to me. I can’t imagine that actually working.
James: Yeah, I thought that was probably your best point in the whole Ruby Rogues discussion, was where you said “in the real world we have to run Rails, and Rails uses every dirty trick in the book, and how are you going to support all of that?”. And the answer is I think it would be really difficult. But, you know, again, stuff like Dialyzer where you just add the types in, and in places where it can determine that there’s mismatches it talks about it.
So it basically assumes success, but then when it can tell itself that this is not exactly right, then it chooses to complain. And it seems like an approach like that could be viable for Ruby, where if we can’t know then we just assume it works, but if we can see some obvious mismatch then we point it out. That would catch some level of errors; the question is, is it a useful level of errors, and would it be worth the pain of going through and adding those type annotations everywhere? Especially to a project like Rails. I’m not sure.
Tom: Right. That’s an interesting idea — to do that Dialyzer-style, third-party static analysis. To say: we’re not going to make this part of the core language, we’re just going to have some kind of piece of tooling that is standardised and some convention for how we write documetation comments, which I believe is how it works in Elixir, right? It looks like a doc comment that’s got the type signature of the function in it, and then Dialyzer can pick that up and do its static checking. Whether a tool like that would work with a more dynamic language like Ruby would be an interesting thing to explore, and I’m sure people have built things like that in the past, but until something like that gets some degree of blessing, it’s not going to have enough traction to be useful, I don’t think.
James: Right, yeah. That’s a good point.
Tom: And that takes us on very nicely to your second point!
James: That’s right! I thought that was a pretty good transition on your part.
Tom: Yeah, classy segue. My first question about this is… I wasn’t quite sure what you were saying. In the episode I had mentioned that the Ruby community is very lucky to have Bundler, and Rust is very lucky to have Cargo, and I mentioned a couple of other fine pieces of tooling, and you came back and said you’re not sure that “luck” is the right term for that. And you also made some points about worrying about that hello world problem of, as a beginner, how do I get started, how do I begin a project in programming language X. So could you just elaborate a bit on what your thoughts were here, and why you’ve taken issue with my correct opinions?
James: I remember when I was originally listening to the episode I got the impression that you were like, “it’s great to have all this tooling”, and you were very pro-Bundler, and you did mention how nice Cargo is. But I kind of felt like you felt the flip side of that was this freedom to choose and organise code however we want, and throw files anywhere, and be able to just make all the choices that we want to make. I’m not sure if that actually me misinterpreting you a little, because, when I did read back through the transcript — which is what inspired my tweetstorm — I less felt that. What do you think about the value of those tools and how important they are?
I don’t know how to do that now, because I’m too frightened by it. And it’s probably way past time for me to get back into looking at what people are using now. I mean, from where I’m standing — which is quite a long way away — it feels like maybe everyone’s using Webpack now? Maybe that maturing has happened, and everyone does just use Webpack. But maybe that’s not true — maybe if I get closer to it again, it’ll turn out that there is still a confusing diversity of ways of doing things.
So I’m definitely predisposed to using languages and tech ecosystems where it feels like that stuff has been figured out, and also in particular where it feels like there’s some kind of official position. That’s why I like Bundler and Cargo, is because it feels like they are in some respect semi-official. I don’t know why I’m saying that about Bundler, there’s not really anything official about Bundler, except that Rails chose to use Bundler, and then everyone else just said “well, this is obviously how we’re going to do things now”.
But, yeah, what do you think? What do the situations feel like in the languages that you use? You’ve mentioned Rust and Elm and Elixir; do you you feel like they’ve got the same maturity of tooling that Ruby does?
James: Yeah, so, I would say they’re way ahead of Ruby. I feel like a lot of modern languages — and I don’t mean to come down as negative about Ruby here, obviously I’ve been a Rubyist forever and I love it, but — the tooling, I think… really Go probably started the trend of really shipping great tools with the language that solve a lot of your problems. But I’ve seen tons of languages following suit: Rust has Cargo, like you said; the tools that come with Elm, the
make and those are pretty good, or
reactor that reloads files as you work.
And then in Elixir, which I’ve spent a ton of time with, they have Mix, and it’s baked right in. It builds your whole projects; it handles dependency management like Bundler; it handles how to kick off your project; it lays out the directories for you, like Rails-style; and really just goes all in. And the more I work with something like that, the more I just love it and really miss it when it’s not there in Ruby.
And I’ve actually seen this when I teach a lot — and I have taught classes in Ruby and Rails for years and years — and it’s so funny to me sometimes that Rails is kind of this behemoth, and it’s this huge complicated system. But you show it to people, and you start explaining the conventions that it follows, and they start building apps right away, and then sometimes they’ll have a really simple thing — they’ll be like “well, I want to do this little JSON API or whatever”, and I’ll be like “oh, let’s just use Sinatra or Cuba or something tiny”, and they lock up, they totally freeze, and all of a sudden they can’t build web apps any more. And I’m like, “what happened? what’s wrong?”, and they’re having all these internal arguments about “well where would I put the file?”, or “how would I wire all this up?”.
James: And that’s kind of true. Like, you talked about how it’s scary and confusing and overwhelming to be faced with all these choices, and it’s like: why do we need to worry about where we stick the files? It’s 2016, it feels like that ought to be a solved problem. And the load path! You know, I was a Java programmer way back when. I’ve had my fill of playing with
CLASSPATH and don’t want to do that any more. Just link everything up and make it work for me, please.
I really love the first-class tooling and I want to see us go more that way, because I think it leads to more standardised things. When I pull down a library in Elixir, I know where the pieces are, and what to go looking for, and there’s no random “this person decided to use the folders
c to divide things out”. And I just know how it works and I know how to integrate it with my system because all that’s been decided and I don’t have to worry about it any more. And I think there’s a ton of value in that.
Tom: Yeah. And I think that all of these things take a really long time. It’s really surprising. For whatever reason, it feels like the actual bare metal technologies that show up in programming languages seem to advance a lot more readily than the things that surround them, like this tooling and stuff. I think it’s a lot easier for the people who build these things to make a new programming language or make a framework than it is for them to take that step back and think about “how should the packaging system work?”. In some respects it feels like — again, going back to Rust — it feels like maybe Cargo benefited a lot from Bundler.
Tom: Bundler had, from my perspective, quite a long gestation that was troubled at times. I remember when Bundler first came out I had a lot of problems with it, and there was a lot of back and forth about what the syntax of the Gemfile should be — how does the dependency resolution algorithm work? how does it deal with conflicts? what happens when you need to upgrade the gems in your Gemfile? — and stuff like that, and that all eventually came out in the wash, but I don’t think it was necessarily obvious to anyone at the beginning how that stuff should work. And people still get confused or disagree with each other about: what should you do with your lockfile? Should that be committed, or not, or should we share that on the team, or what if it’s a gem?
And there’s a real fractal of detail there around how do you make something that’s not only technically useful but is also practically useful because people can understand how to work with it and people can get it. From what you’re saying, I get the impression that those kinds of considerations are being addressed better in these newer languages that are being built by people that just have more experience with all of that stuff, because I think in the past — like you say, with something like Java — my experience with Java has always been very much that I just spend a lot of time fiddling with what feels like irrelevant stuff like making sure I’ve got the right
CLASSPATH set, or making sure I’ve got the right stuff in my properties file or whatever, and that increasingly feels like irrelevant busywork that I shouldn’t really be having to spend my time on.
And if we can have newer languages and newer platforms that anticipate those needs and just give you a better out-of-the-box experience, that does seem like an obvious win, but it also means that there’s a lot more… you know, it takes a lot of time and energy to build those things, right? Like, that’s a whole job in itself: to maintain that build tool, or that dependency manager, or that package manager or whatever it is. That’s a job of the same order of difficulty as maintaining the programming language itself.
James: Yeah, that’s probably a really good point. Bundler is an amazing tool, and I love it, and nowadays I think it just doesn’t go far enough. I want it to do more, you know. And I think probably one of the major differences there would be languages like Ruby, where it got added on after the fact, as opposed to languages like Go or Elixir where that was there from the get-go. The tooling was how you interact with the language.
And maybe that makes it easier to do those kinds of things because, if you were to come in to Ruby today, and you were going to be like “I’m going to make a Mix-like tool” — Mix being the one that’s in Elixir — you would have to be “well, how am I going to support this current ecosystem where there are no rules, and anybody can do anything”, you know, and that would be a very very hard problem. But if you start from the beginning and you say, “well, when people write Elixir code they’re going to be laying it out as a Mix project, and that’s going to look like this, and that’s how it’s going to work, and it’s always worked that way”, that’s probably a much easier problem to solve, I bet.
Tom: That’s a good point. Maybe we just need to keep inventing new languages and making these decisions earlier and earlier in the gestation of those languages so they get baked into the conventions of the community for that platform, whatever it is.
James: Ooh, is that another segue?
Tom: Well, it may be. I’m heading in that direction.
The Beginner Experience
Tom: I did have a kind of a tangent that I wanted to ask you about.
James: Go for it.
Tom: Why is it that the people who develop these things don’t necessarily understand the difficulties that beginners have? I think that there is a problem with these tools that are built a long way into the lifetime of a particular language, let’s say, which is that those things are necessarily built by someone who already has a lot of experience in whatever that language is. and I’m not thinking of any particular project here, or any particular language, but just my experience in general; like you said, I work with beginners a lot and I do a lot of teaching, and I just see over and over these problems where a lot of the tooling in more mature languages that have had tooling added on afterwards tends to assume a degree of competence with that language, or an ability to understand what’s gone wrong and work around whatever problems come up, that isn’t really appropriate.
Quite often the people who are using those tools are the people who are most in need of something that doesn’t assume any knowledge, and it’s just interesting that in something like Ruby… So you mentioned things like load paths, and how to start a project, where files belong. That’s something where, if you’ve been using the language for a long time, all of that stuff is just in your muscle memory.
And you know what the conventions are about “oh, I’m going to make a folder called
lib, for example, and I can reasonably expect some other thing, like RSpec, to just automatically put
lib on the load path because that’s how things work in Ruby land, and actually there’s nothing magical about that folder, I could call it anything, but then I’m going to have to either manually put it on the load path or I’m going to have to pass
-I to the Ruby interpreter to tell to put that on the load path, or I’m going to have to use
require_relative to tell it where to find the thing I’m trying to require”.
All of those little accumulated bits of cultural knowledge, it feels like some of that stuff gets rolled up in the tooling that gets developed when a language is mature, because the more senior people who are capable of developing tooling have all of that stuff engrained in their understanding of what it means to be a user of language X, and so a lot of difficult stuff gets rolled up in it.
James: No, I think you’re dead on there, and I’m not sure if it comes from what I said earlier in that they see the existing ecosystem and they think “oh, we have to support all of these niggly bits that exist today, and give people still the freedom to do absolutely anything they want to do”, or if it’s more what you alluded to, of, they’re made by power users for power users. I’m not sure if it’s one of those, both, or something else entirely.
But I think you’re right. The complexity of systems like that is definitely higher. And like, you know, RubyGems, which I don’t mean to say bad things about RubyGems — I love it, and use it all the time — but if you go read documents of how to prepare gems, it’s just a long description of all these little things you should do, you know? And the whole time I’m just thinking, you know, you could just do these for me, and that would be great.
James: I think we have a fear that — maybe you’ve seen Mix, you know, the Elixir project system that ships with the language itself…
James: …or Cargo, or something. Maybe you’ve seen these and you think, “oh, that’s going to take away a lot of freedom from me”. And mostly I think that’s just fear that’s not founded on anything. It doesn’t take away any freedom from you. What it mostly does is mean there’s a whole bunch of things you can just check off because you don’t have to worry about them any more, and they’re already taken care of for you, and they’re already there and solved and you can just go with the flow and work.
You can still write scripts if you want, if you want to do some fancy command-line hacking, you just want to throw some code in a file and run it outside of Mix without worrying about Mix or whatever, you can still totally do that. So I don’t really see that they’ve lost any freedom and they’ve gained so much, and the language can just assume that people know that, because all the documentation shows you, you know: just make a Mix project and do this, and this is where you stick that. I think it’s very beginner-friendly. So, yeah, I would love to see the tools move more in that direction for sure.
Tom: I think there is a slow, gradual realisation that people choose programming languages and web frameworks and libraries based more on a gut reaction to “what does the website of this thing look like?”, “what does the documentation for this thing look like?”, “what do the tutorials look like?”, “can I just
brew install foo, and then type
foo, and then I’ve got a REPL where I can just try out this programming language?”.
Like, those things are… in one respect they’re superficial, because you could have the most amazing programming language in the world that was just an undocumented repository on GitHub, and as long as you know how to clone that and build it with GCC and run the compiler and use it, it doesn’t make it any less of an amazing programming language, but I think that people don’t make decisions on the basis of some idealistic, nebulous concept of “what might this thing be able to do for me?”, they just make decisions on the basis of what they can see and what they can rapidly do.
And like, you see that now, where people are investing more time and more energy specifically in documentation projects and redesigns of home pages, because that’s the stuff that actually matters to people. They want to be able to see, “does this thing have up-to-date documentation?”, “does it have modern tooling that will solve problems like package management, or am I going to be spending days thrashing around trying to figure out how to install a dependency?”, you know?
So I’m really happy to see that people are very very gradually coming around to that realisation that you can’t get away with just being technologically sophisticated, you have to be culturally sophisticated in the way that you present your thing to the world, and to give beginners — whether they’re new developers, or very experienced developers who are looking at your thing for the first time — you want to give them that great first experience.
In the same way that Stripe have completely taken over payment processing: I don’t know whether Stripe’s product under the hood is technically any more or less competent than anyone else’s, but they’ve got really great documentation, they’ve got a code snippet on their home page that you can copy and paste and you can just try it out, and that wins more people over to their service than anything about how much it costs or how fast it is or what they’ve implemented it in, because people just care about the experience, and loads of industries have understood that over the last few decades, and it feels like the world of open source programming languages is finally being dragged kicking and screaming into that user experience world. And it’s a relief for me to see that happen.
James: Yeah. I love it that you mentioned documentation and harped on it so much. Here’s a fun exercise. We have all these Ruby version managers, you know — we have rvm and rbenv and chruby, and now there’s even others that are multi-language. I’ve been using asdf, which I use to manage Elixir and Ruby and Node and a bunch of other things.
So you have all these version managers and it turns out that a lot of them — maybe they’re getting better now, but in the past, and I know even asdf currently because I ran into this recently — a lot of them, in order to gain speed on install, they disable the documentation by default. So if you build Ruby, or install any gems or whatever, they put these little switches in there to make sure no documentation gets built.
And I mean, think about that trade-off for a minute. How often do you install the language compared to how often do you need documentation, you know? And what is the experience to the beginner in that scenario? Well, now you have this amazing great powerful language; hope you don’t need to learn how to use it or anything!
James: So weird!
Tom: It is weird. And it’s easy to forget that it doesn’t really matter how long you’ve been doing something, it doesn’t really matter how long you’ve been a developer. You always find yourself — or, speaking for myself, I always find myself in that kind of beginner situation. I mean, even with Ruby, I keep finding new things, or I keep thinking “oh, I’m going to try this library that I’ve never tried before”, and then I’m just right back in that situation of someone who’s never used the thing, saying “okay, I can see how to
require it, but then what do I do? What’s my mental model for this thing? How do I get started with it?”. I never stop feeling like I need the help, you know? I always need the help.
James: Yes! There’s an awesome thing I see: I work for NoRedInk, where Evan… I’m not going to try to say his last name, because I would totally screw it up, and then he would just beat me up at the next company retreat. But the creator of Elm works with us. Evan. And I watch him a lot, and one of the things that I see, that I love that he likes to do, is he loves to pair with beginners. People that have never seen his language.
He’ll sit down with them and pair with them so that he can find all the things they’re running into and get stuck on. And he’ll make notes, he’ll be like “oh, that should have been more obvious! Why didn’t I think of that?”. And then he’ll go back and make that more obvious. Or, “oh, that error message was terrible! It didn’t tell them what was really wrong.” And then he’ll go fix the error message so that it points you exactly at what you did wrong.
I can tell he spends a lot of energy keeping in touch with that mindset. And even though he’s past it and can’t easily put himself in those shoes any more, he makes sure he’s around people that are experiencing those things so that he can see them and react to them and make their experience better, and I think that’s amazing.
Tom: That is great.
Different Languages, Different Perspectives
Tom: So you’ve done some Elm. In fact, in my mind you represent — out of all the people I follow on Twitter — I think you’re one of the people who is the most promiscuous in terms of the languages that you use.
James: That’s awesome.
I think I said something about not wanting to get into a situation where I was a perpetual junior developer, where I was always “I’m going to go and spend a week with Elixir and just be a junior Elixir developer”, or “I’m going to go and be a junior Elm developer or a junior Clojure developer”, or whatever it is. That seemed to rub you the wrong way somehow, so I just wondered if you could expand on your feeling about that. Obviously you do do that thing where you jump around between languages and I’d like to hear more about why you do that.
And I don’t typically do that. I play with languages all the time, but if you look, I’ve been present in the Ruby community since 2004, so for twelve years, ish, Ruby has been my primary language that I’ve used. But I’ve played a lot with other languages along the way. I feel the value in it is huge because every language has its different take on things, its different things that it does really well, and you get to see how that works really well and what that’s like and why that’s great.
Rust, you’ve got the low-level programming that’s actually safe, you know? I used C plenty of times, and I’ve horribly crashed my computer in the older days because of errors where I was writing to memory that I shouldn’t have been writing to, and stuff like that, and felt that. And then Rust just has this great language that protects you from yourself while still allowing you to do low-level programming, and I think that’s super cool.
Or all the different functional languages. If you spend all your time in something like Ruby, and really working with it, then you need to go try some functional languages. You need to see what they’re doing differently. Their data structures are very different: in Ruby we worry about arrays; in functional languages they worry about linked lists; and those are not the same thing, and you can’t use them the same, and some things linked lists are great at and some things they’re terrible at, and similarly with arrays.
And just the exposure to all of that, you know. If you’ve played with concurrency in Ruby it’s interesting: you can spin up threads, or you can spawn processes, and those are different paradigms; the threads reminding me more of Java, and the processes being more like a Unix-ism. But then if you go play with Erlang or Elixir, where you have real concurrency — a language designed around that concept — and you see what that looks like and feels like, that’s a super interesting thing.
And I don’t think spending time doing that… you know, it increases your perspective. So even when you come back and write Ruby, you may try a little more immutability in your data structures, because you become a little more scared of state. Or if you are going to do something concurrent, you at least know that the number one thing you should try to do is share nothing, you know, because that makes things so much easier. And you bring those values back or take them to whatever you do next, and I think it really helps you get further along.
I compared it to your book in my tweets — Understanding Computation — where you really spend a lot of effort helping people understand how computers churn through stuff, and figure stuff out, and what the rules of that are. And when you understand those rules, then you can say more about computers, like how you say “well, you know, static analysis is great but obviously there’s a limit to it, because I understand the halting problem, right, and that’s not going to get us all the way there”. And I think all of that understanding adds up and makes you a better programmer in the long run. So I don’t think it’s just about chasing what’s how right now.
Tom: Well that’s a really good point, and I think I’m going to have to concede this point of the argument to you, because I think you’re right. I listened back to what I said, and even I found myself disagreeing with it a bit. I think I maybe didn’t express myself very well, because, like you say, I definitely feel that it’s worth exposing yourself to all of these ideas.
The easiest — or perhaps the best — way to expose yourself to all those ideas is just to try out a bunch of different programming languages. If you want to see what a different model of concurrency might look like, there are several ways that you can do that. You can read Tony Hoare’s CSP paper, where he invented the whole idea of communicating processes that work in that particular way, or you can sit down and use Erlang or Elixir and actually use a language that embodies those ideas.
Like you, I believe that that’s incredibly valuable. I think I was maybe expressing some slightly curmudgeonly world-weariness, where I felt that I didn’t really have the energy to keep picking up new languages all the time. And partly that’s an expression of slight envy for people like you who are able to still be doing that, to keep picking up that new stuff, and you obviously have the energy and the curiosity to go and build stuff in Elm and Elixir and Rust and stuff like that, where I don’t necessarily… you know, all of those languages are things that I’ve installed and I’ve fiddled around with, but I’ve never gone and bought the book and read it properly and tried building a proper thing with it, just because I find it difficult to invest very much of my effort into those things.
But, I think that that’s partly an expression of the fact that I feel like I’ve got to a point in my education, or my career, or both, where the ideas in those things are things that I have, by now, sort of been exposed to. So I mean, all of the stuff that you mention there about, for example, functional programming and immutability and stuff like that, I’ve had the luxury in the distant past of having time to sit down and learn all of that stuff, and think about it. And one of the things that I do at the moment is try and give talks and help people one-on-one to see those ideas in a programming language that they’re already comfortable with. So I’ve done stuff in Ruby, for example, where I’m showing how to do really extreme functional programming, like just with the lambda calculus.
Tom: I fairly recently gave a talk about how to do logic programming in Ruby by implementing μKanren. And so just sort of trying to import those ideas into an environment that people are already familiar with, and that’s an alternative to saying “well why don’t you just go an learn Haskell?”, or “why don’t you just go and learn Prolog?” or something, and I think the benefit is still partially there.
But I’m still basically in agreement with you. I think you’re right that everyone should — in the same way that it’s generally a good thing to be a voracious consumer of any kind of thing, you know, you should read all the books, and you should see all the movies, and you should listen to all the music — I think you should try all the programming languages, you should go to all the countries, you should meet all the people. It’s a good thing to be well-rounded and to expose yourself to those things, and it helps your mental immune system to expose yourself to all of those ideas. Because some programming languages contain bad ideas, and once you’ve identified those bad ideas, that can help you to avoid mistakes just as much as it can help you to home in on good ideas, I suppose.
James: Yeah, there’s so much out there to see, and I totally get what you’re saying from the weariness of… our culture definitely pushes you, like, “oh, it’s 2016 and you’re still a Ruby programmer, what’s wrong? Are you clueless? Have you not realised that all these languages have replaced it? You have to pick one and get there!”, you know. And that’s just silly. You don’t have to switch to a language to appreciate it, or anything like that. And you don’t even have to go neck-deep, you don’t have to read five books about a particular language, especially if you’re not interested. I would just say: go far enough to get to the core bit, the thing that language really nails.
If we’re talking about Elixir or Erlang, it’s the processes and the concurrency. If it’s Rust it’s the safety and how helpful the compiler can be. If it’s Elm it’s how you can write programs and never have to think about
nil ever ever again. Things like that.
nil is even an interesting point, because I think a lot of people know that it’s bad — there’s been a lot of writing about it and such — but there’s kind of a spectrum. When I program Ruby, I do find
nil bad, and I spend a lot of energy trying to avoid it, like
fetching things out of a hash instead of using the brackets so I don’t accidentally pick up a
nil that bites me later.
I feel like a lot of Ruby best practices are designed to protect us from these things. And then you can work with a language like Elm where you don’t even have
nil so it’s definitely not a problem, or you can work with a language like Elixir which is kind of interesting because Elixir has
nil, just like Ruby, but I don’t find it to be very much of a problem at all there. And the reason is that you’re almost always going through pattern matching at the beginning of a function, and so you pattern match out and verify and basically destructure what you were given into exactly what you think you have. And so if you have a
nil it gets caught there and it doesn’t sneak into the system and get around.
So with a language that has very pervasive pattern matching, especially in choosing what gets called, like Elixir does, I find
nil much less of a burden, and it doesn’t bother me. And you can’t see all these things until you go and you play with these things. But it does require an investment of time, of course, and that’s unfortunate. And don’t buy into the hype that believes now you have to totally convert and never touch the other thing again. That’s just silly.
Tom: Right. And I think that’s partly what I was reacting against. I think I said in that Ruby Rogues episode that I thought there was too much focus on “which ring do you throw your hat into?”. You know, that you have to join a tribe, and then once you’re in that tribe that’s who you are, and I’ve never really felt like that with programming languages. I feel like my pragmatic decision about “what tool am I going to pick up today to get the job done?” is not really an inherent part of my identity, it’s just a decision I make every day, and as long as I’ve got a tool that works fine I will probably keep making that same decision.
But I’m always prepared to just change my mind very quickly and switch over to something completely different if it turns out that that’s beneficial. And I’ll still have my friends from the Ruby community, but I don’t feel like I have an emotional investment in any particular choice. It makes me sad when I see people arguing about this kind of thing, or trying to make each other feel bad because they’re an X or a Y programmer. It seems counterproductive, I suppose.
I think it’s difficult to strike that right balance between making sure that you don’t buy too much into that kind of programmer tribalism, and start identifying too much with a particular language, but also making sure that you’re well-read in programming languages. I think there is a happy medium there; it sounds like we’re both kind of in agreement about where it is and what the benefits of it are.
James: Yeah. I love what you said about an “X programmer” or a “Y programmer”. People should never, ever label themselves that way. People say all the time “I’m a Ruby programmer” — you should not say that. You’re a programmer. The specific syntax that you’re using at any given time, that’s like the smallest part of programming. Ruby has 63 keywords or something like that — maybe it’s more now, but it’s not very many, it’s not like it’s a huge language — and Ruby does have a lot of syntactical edge cases, I’ll concede.
But a lot of languages are drastically simpler than Ruby. Like Elm and Elixir both, their syntax is very easy to pick up. There’s not much there. Once you’ve learned how to think like a computer, that’s the hard part of becoming a programmer, and going to all the different languages, that’s an easy step from there. You just have to figure out what they do slightly differently and stuff. But you understand the idea of computation and logical modelling and stuff like that, and that’s the way hard part of being a programmer. So yeah, don’t sell yourself so short to tie yourself to one language.
Tom: Yeah, I agree. I would add, as I said before, the point about the metacognitive skill of being a developer. Like you say, there’s all of that technical stuff about knowing how to think like a computer, knowing how the hardware works, knowing the techniques and the tools that you can use.
But also, the thing that I always struggle to teach people is that higher-order thing of: how do you approach this? What is the process for writing software? What should you do when you’re sitting in front of your computer? Even if you’re really good at writing individual lines of code, how do you know what individual lines of code to write? What order do you write them in? How do you organise the larger structure of your program? How do you do design? All of that kind of stuff.
That all seems almost entirely universal between all of these different languages, and the challenge of trying to be methodical and trying to be disciplined when you’re writing code is something that, once you’ve developed that feel, you can switch to a language you’ve never used before, and even if you are struggling with the syntax for the first few hours that you’re using it, hopefully you still have all of that other accumulated ability — as you say, all of that knowledge of the machine that’s helpful, but also all of that knowledge of your own mind in terms of “what’s the right way for me to make progress on a program without disappearing down rabbit holes and blind alleys and making a load of obvious errors that I’m going to have to back out later?”. All of that stuff you carry with you from one language to another, I think.
James: Yeah. I totally agree. And I’m surprised how far it goes. I was a huge OO programmer in Ruby just a couple of years ago. I really had the focus of object-oriented programming. Lately I’ve been playing a lot with functional languages. And when I first tried that, I was like “wow, this is totally different, and none of my knowledge applies”.
But then, as I got further into it and just learned to squint at the code the right way, all of a sudden I realised, no, everything I learned in object-oriented programming still applies, it just looks a little different over here, and you have to think about it slightly differently. But the goals are all still exactly the same: I’m trying to make code that I can understand and follow and maintain and manage. It’s all the same.
Entirely Different Methods of Design
Tom: Well, you mentioned Elixir there. Just briefly, you wanted to argue with me about the actor model and Erlang-style concurrency and that kind of thing. Because I said a couple of glib things about “well, if you’re just making a simple web application, you don’t need actors and all of this elaborate concurrency, you just need a single process and you can always scale horizontally by spinning up a thousand processes”. But you had some arguments against that perspective.
James: Yeah. So obviously you’re right if you’re just doing a simple blog or something like that. But think of the average web application these days. Let’s say something like Rails.
We run Apache in front of it, because we’ve got to get all that static file stuff and gzipping concerns out of that single process you’re talking about. We run four copies of Passenger or Unicorn or whatever — Puma — behind that, because we need to be able to round-robin all those requests and service them in parallel.
And then they’ll keep their memory in the database, because we can’t keep memory in that single process, because you may not end up back in the same process. And then we’re going to have to make all of this go faster, so we’d better throw some memcached in front of it, and make sure we store some stuff out-of-band over there.
And it just goes on and on. Like, there’s all these things we do to make that single process model work, because it doesn’t work all that great, you know? There’s a lot going on there, and being able to squeeze all that in is really tough.
Whereas it’s almost comical — there’s a book, I think it’s “Elixir in Action” — early on in the book it has this chart, and it’s like, if you’re building a web app in Ruby, you use Rails and use Redis and it lists all these things you can use. And then in the adjacent column, it’s like, if you’re building it in Elixir what would you use for caching? And it’s Elixir. What would you use for the background jobs? And it’s Elixir.
So on Ruby you have like ten different tools to accomplish this request, and on the Elixir side it’s like, yeah, the language takes care of all that kind of stuff. So concurrency’s just a solved a problem there. And being able to throw off another process that does something and then eventually gets you an answer is trivial, and so you don’t have to worry about those kinds of things.
I’m not saying that you have to have that to build a web app — obviously you don’t — I’m more saying I think you might be surprised how ridiculously useful it is, that you can skip a lot of things. Like, memcached is the ability to put this stuff in an easier-to-access level, but in Elixir or Erlang you have this built-in system called ETS which is a term storage that’s built into the language itself. It’s almost exactly memcached, and you can store and cache stuff there. While it’s not required, it’s surprisingly useful.
Tom: So this seems like it’s at least tangentially related to the tooling situation. You’re saying that there are more batteries included out of the box when you get this thing. In this case, because of the underlying abstractions, because of the underlying philosophy of the language, you can get a bunch of this stuff with less effort than you would otherwise need in a more conventional setup. That sounds great.
Me not immediately realising that is purely an artefact of me not having spent serious time with Elixir. Like, I sat down and I have played with it. To me it appeared to be a very nice modern functional language. It’s got all the features I want to see in a functional language: it’s got pattern matching, as you mentioned; everything’s immutable; it doesn’t have any loops in it, you know, everything’s done through recursion; it’s got Erlang’s “let it crash”, this thing’s just going to run forever and if processes die then they die and we’ll cope.
All of that stuff that it does seems really great, but I don’t know about the actual operational efficiencies of doing stuff on a platform that has a thing like memcached built into it. That sounds really good.
When you tweeted at me you mentioned databases. Obviously a database is kind of like shared memory; it does other stuff as well, it serves complex queries and it deals with persistence and stuff. In the Erlang and Elixir communities, is there some kind of standard replacement… like you say, there’s something that can just do what memcached does; is there something that can just do what Postgres does? Or do people just use Postgres?
James: It’s an interesting question that you ask. You actually stumbled into one of the most interesting aspects. To answer your question: there are completely replacements. Erlang ships with a database called Mnesia, like “amnesia”.
Tom: That’s reassuring!
James: Yeah, right? It’s built around ideas like clustering and stuff, because Erlang can just do that basically for free. I wouldn’t claim that it’s a Postgres by any stretch of the imagination. It’s not. It’s a much smaller, simpler database. But it is a database, and it does have some properties that are common in databases.
But maybe another, even more interesting thing: a lot of the Erlang programmers that grew up writing code that ran forever, because their system literally allows that. You mentioned the self-healing, the “let it crash” and being able to replace it, with the supervision and stuff that they have — they have hot code reloading so they can have a program running, and instead of stopping it and starting the new one they can literally replace the code while it’s running and migrate the data so that it deal with the changes that you’ve made in data structures and stuff like that.
And because they were writing these things that ran forever, one of the questions experienced Erlangers like Joe Armstrong ask us younger web developers is “why are you always sticking everything in a database? What’s that about? Why do you have to do that? Can’t your code just run forever, and then you won’t need one?”.
James: We’re actually working on a tool at work right now where we kind of need to keep these two separate systems in sync. And it’s like a thousand different things on one side that we need to keep in sync with the other side. And we’ve found that we can model it in Elixir just by spinning up a process per thing.
So yes, we’re spinning up thousands of processes, and they just sit there and hold the state of that thing in themselves, and then periodically they make sure that that state is still in sync on both sides, and if it’s not they trigger the needed changes to bring them into sync. And it’s just ridiculously simple: we don’t need a database, because the truth exists already on both sides, so there’s no danger of us losing any data; and we didn’t have to think out this complicated thing of “ugh, how are we going to take this giant wall of data that represents the entire state of the world on this side, and then get the giant wall of data from the other side, and compare them, and put them back together”.
Instead it was like, well, it’s just a thousand different things, so let’s make these tiny little pieces of the system that are just responsible for keeping their one little thing in sync, and let those all just run forever and take care of things. So like, it leads to entirely different methods of design. And that’s what you really pick up from playing with stuff like this.
Tom: It’s definitely a different way of thinking about the overall architecture of your system. To go from this situation where you draw a diagram where you have lots of different boxes with different words written on them, that are connected with lines — the database is over there, and the Redis server is over there, and the memcached server is over there, and here’s Apache or nginx and all of those other pieces — to essentially saying, we’re just going to have this homogeneous system where we’ve just got a bunch of lightweight processes, and some of them are responsible for representing state, and some of them are responsible for storing transient bits of information, and some of them are responsible for doing processing.
But sort of sweeping everything into that one central place and saying, well, everything is just going to be an Erlang process, essentially, something that’s running on BEAM or whatever, and just getting on with the job of doing one of the things that our application wants to do. I mean, that sounds like really interesting, different way of architecting these applications that I thought were kind of boring and everyone knew how to do them.
James: Yeah, you can… you know, you have ideas like event sourcing which I think primarily came out of the domain-driven design crowd, where you essentially treat changes to something as a series of events. So instead of actually modifying the thing in the database all the time, you build up a table of “well, it was this, and then it changed to that, and then it changed to that, and then it changed to that”.
One of the problems you run into with these systems is you have what state they’re currently at — you know, in order to figure that out you’ve got to go to the beginning and play through all those events, which you can do but it’s slow. So then, you know, you come up with a lot of workarounds of how to do this and what to keep track of. Or, you could just have a system that’s running forever and it keeps the current state of the world in memory. So it knows where things are right now.
But it has that event sourcing style database log somewhere that it’s just treating basically like a log, where it’s logging as things happen. And then if your system does go down, for whatever horrible reason, and you have to bring it back up, then you go through the slow process of rebuilding the state of the world to get back to your current thing. But in your day-to-day operation all you do is keep the current state in memory and write these tiny log messages, and it greatly simplifies a lot of things.
And I’m not saying that’s a cure-all, of course it’s not, but there’s areas where you can build it like that and it leads to exciting things, like web applications that are different from what at least I’m used to seeing.
Tom: Interesting. You win!
James: No, I think what we found was we agreed on pretty much all of it.
Tom: I think you’re right. James, it’s been really interesting to talk to a voracious polyglot programmer like you. This has given me a lot to think about, and I’m glad that we have reached some kind of mutual understanding of these things that I thought we might disagree on, but actually we’re just both looking at the same thing from a slightly different angle.
James: Yeah! I do too. I’m really glad that we had this conversation. It was very thought-provoking for me.
Tom: Well, thanks very much for making the time to talk to me. And I hope that you continue trying new things and telling me about them so that I can get the benefits of your wisdom without having to invest any of the actual effort and intelligence of learning them myself.
James: That’s exactly why I’m doing it, yep. I’m trying to save you some time.