Jamie Gaskins

Ruby/Rails developer, coffee addict

Ruby Warts

Published May 16, 2012

I love Ruby. I've been developing in it as a hobby for 8 years now and professionally for 3. It is still my favorite language to do absolutely anything. But even with that in mind, it has some things about it I'm not sure I like.

Ordered parameters

Note: I use the words "argument" and "parameter" interchangeably here.

This is an implementation detail that frustrates me to no end. Every time you add a new parameter to a method, you either have to add it onto the end or you break its interface. This is an easier thing to do when you're first creating that method, but if it's being used in the wild, changing its interface will piss off a lot of people.

Additionally, default values for parameters have to come at the end. You can't have an optional param with a required one after it. Here is an example of how Ruby works around that. The first parameter can either be the separator or the limit, based entirely on its class. This is a horrible idea. There are very few good reasons to ask for an object's class in Ruby and branching on behavior isn't one of them.

Ordered parameters are a relic of systems programming languages like C, where arguments must be in a certain order because they are then pushed onto the stack for the function being called. We have no reason to keep ordered params other than "that's the way we've always done it".

Granted, for single-arg methods, it's very, very convenient not to have to write the parameter's name. For example, array.find(value) is awesome, and if there's no ambiguity, it's perfectly reasonable to go without naming.

Rails solves this by using a hash of arguments for most of its arguments and this has now become a common Ruby idiom because of it. This is a bandaid solution because we end up having to get the values of these arguments manually from the hash. It's an improvement for methods that take many arguments, but it could be improved by adding support directly into the language.

Cannot override shortcut operators

In Ruby, you can override damn near anything. Operators like && and ||, however, are stubborn. Their functionality is hard-coded. Implementing them as methods on the object would be trivial:

class BasicObject
  def && other
    self ? other : self
  end

  def || other
    self ? self : other
  end
end

That would provide the short-circuit behavior of those operators and allow for overriding. This isn't a major issue and there are very few reasons you'd ever want to override them. I only came across it while trying to create an Array-like interface for Perpetuity's mapper-query syntax (something like ArticleMapper.select { |article| article.published: true && article.author_name == 'Jamie' } to feel more like Ruby and less like ActiveRecord).

Methods with questionable return values

I mentioned above the IO#readlines method. Its return value also frustrates me; it's an array of lines with each line containing its separator. Surely there are times when you would want to keep the separator, but I have yet to come across one in 8 years of Ruby (and even in Perl, gets calls were almost universally chomped). The general case is that you just want the line's content. You could even provide an additional argument to keep the line separator if you like.

When I first saw the method, I figured that it would be shorthand for io.read.split(separator) (IO#read slurps in the entire file to a string), but in reality it's implemented as a loop of IO#gets which appends to an array. After benchmarking, I found that io.read.split(separator) was faster, but it's impossible to get it to return the current implementation's return value including the separators. String#split doesn't have functionality for including the separator. These methods should do the same thing, even if they do it on different types of objects. Least astonishment was violated here.

Syntax-related stuff

The syntax is mostly awesome, but there are a few things I think could be improved.

Parentheses

Consider the following:

names.split(", ")

Why do we surround params with parentheses? I know we can omit them the majority of the time Seattle-style, and this is my preference, but why do they even go there? Personally, in my Ruby code, the only reason I don't write (names.split ", ") is that it would raise the hackles of most Ruby devs. To me, that makes more sense. You're using names.split ", " as a single value, so why not enclose the whole thing if you need parens?

Dot as a message indicator

This actually doesn't bother me at all, but I wonder about other possibilities. When sending messages to Ruby objects (e.g. calling methods on them), we use the dot to indicate that the token following the object is the name of that message. Smalltalk, Objective-C and Fancy (the latter two being derivatives of the former) all use a space to denote a message.

This would make something like RSpec fun:

names = UserRepository all map &:name
names should include: 'Foo Bar'

I haven't really thought this part all the way through yet, but clearly we'd need something to signify that &:name and include: 'Foo Bar' aren't messages but parameters. Maybe the dot was chosen to make this easy. Matz did list Smalltalk as one of his inspirations for Ruby, so I've been curious about his choice of the dot as a message token.

What do I plan on doing about it?

I don't have all the answers and some changes I'd like to make to Ruby definitely require changes to other pieces of the language. I'm also not confident that my pet peeves or desired features of Ruby would happen any time soon even if I posted them to Ruby's issue tracker. Matz explained that he does not want to make any backwards-incompatible changes in Ruby 2.0. I don't personally agree with that, since top-level version changes are the perfect time to make revolutionary changes (not to mention, there were a few incompatible changes from 1.9.2 -> 1.9.3). I think Matz is great and I'm ridiculously thankful that he brought this awesome language to life, but I disagree with quite a few of his decisions about Ruby. And that's okay; not everyone has to agree on everything.

However, I've been toying with the idea of building my own language on the Rubinius VM, the way that Christopher Bertels did with Fancy. I'm not sure I'm ready to do that yet, but I think it'd be a great academic exercise as well as allowing me to scratch my own itches. One nice thing about using Rubinius's VM would be to be able to call out to Ruby code in if I need to (similar to how you can call Ruby from Fancy or Java from JRuby), which would help give me a push start in the implementation.

I still love Ruby

In spite of all its warts and identity crises, Ruby is still my favorite programming language of all time out of the 20ish that I've used. I've been using it since before I knew what Rails was (possibly before it existed) and I'm sure I'll be using it for a long time yet. Between the language, the culture and the community, I can't imagine anything replacing it in my heart or my work.

TwitterGithubRss