ENOSUCHBLOG

Programming, philosophy, pedaling.


Five Things I Hate About Ruby

Sep 28, 2015     Tags: programming, rant, ruby    

This post is at least a year old.

In the spirit of Brian D. Foy and C. Titus Brown, I’m going to attempt to list the 5 things I hate (most) about my current scripting language of choice: Ruby.

Here we go:

1. Blocks and Procs and Lambdas, oh my!

There are too many incompatible ways to create a closure in Ruby.

Blocks are part of Ruby’s syntax, and are not objects:

1
[1, 2, 3].each { |i| puts i } # => "1\n2\n3\n"

Procs and lambdas are both objects of the Proc class:

1
2
3
4
5
6
7
p = Proc.new { |i| puts i }
[1, 2, 3].each(&p) # => "1\n2\n3\n"
puts p.class # => Proc

l = lambda { |i| puts i }
[1, 2, 3].each(&l) # => "1\n2\n3\n"
puts p.class # => Proc

…but lambdas are different:

1
2
p # => #<Proc:0xNNNNNNNNNNNNNN@(irb):2>
l # => #<Proc:0xNNNNNNNNNNNNNN@(irb):3 (lambda)>

I’m not going to go too deeply into the differences between the three (here is a good post explaining them), but it should suffice to say that having 3 types of anonymous closures is fairly ridiculous. I don’t normally look to Python for good syntax design, but I greatly prefer its singular lambda syntax to Ruby’s hodgepodge of incompatible equivalents.

2. Dynamic Hashes

First of all, Ruby’s Hash class is really cool. It’s my favorite hash table interface, and working with it feels extremely natural compared to hash interfaces in other languages.

Second of all, Ruby’s ability to create dynamic hashes is really cool. They make lazy lookups a piece of cake:

1
2
3
4
cubes = Hash.new { |_, k| k ** 3 }
cubes[1] # => 1
cubes[3] # => 27
cubes[9] # => 729

Unfortunately, as I’ve written before, they’re also extremely limited. For example, you can’t use them to format strings (as you could a normal hash):

1
2
3
4
5
6
7
# normal
"My favorite color is %{color}!" % { color: 'purple' } # => "My favorite color is purple!"

# dynamic
h1 = { color: ['red', 'green', 'blue'] }
h2 = Hash.new { |_, k| h1[k].sample }
"My favorite color is %{color}!" % h2 # => KeyError: key{color} not found

They also simply don’t work with Hash#fetch, although this is slightly more reasonable due to the looked-up key(s) not actually existing:

1
h2.fetch(:color) # => KeyError: key not found: :color

Similarly, the semantics of dynamic hashes are thoroughly messed up. Whether or not methods like Hash#key? should always return true when called on dynamic hashes is up for debate (I personally believe they should), but Ruby’s behavior with respect to dynamic hashes certainly needs to be made more uniform.

3. The Gem Ecosystem

Don’t get me wrong on this one. I really like RubyGems — it was well thought through, is easy to work worth (hooray for working dependency tracking and uninstallation!), and hits that sweet spot between being easy too easy upload to (result: everything is a package, even things that shouldn’t be) and too difficult (result: nothing is a package).

Unfortunately, the Gem ecosystem is pretty awful.

Some major flaws:

1
2
3
4
5
6
my_special_gem
myspecialgem
libmyspecialgem
my-special-gem
mySpecialGem
MySpecialGem

Not only is this awful in terms of Gem discovery, but it also makes finding and using the right Gem a regular hassle. Deciding between gem-for-service and gem_for_service shouldn’t have to be based on which has more stars on GitHub.

RubyGems could fix a lot of these problems by drawing inspiration from the Perl and CPAN, the default and official Perl package manager. There are plenty of things wrong with CPAN (especially with cpan, which has a special place in hell reserved for it if it ever dies), but it also does a lot of things right:

(I remember reading a blog post comparing the favorable aspects of RubyGems and CPAN a while ago. If you happen to know it, I’d appreciate it if you could link it below or get it to me some other way.)

Oh boy.

For some reason, Ruby divides what most other languages call their “Standard Libraries” into two: the “core” and the “Stdlib”.

This organizational decision makes no particular difference to the user, apart from the fact that Stdlib classes usually need to explicitly loaded with require. This essentially makes the Stdlib the only libraries that are guaranteed to be require-able (if not functional) on every Ruby installation (or not, as we’ll see below).

Unfortunately, the Stdlib and its libraries are a bit of a nightmare:

Not everything in the Stdlib is a mess. Some parts, like Net::IMAP and OptParse, are extremely useful and acceptably documented.

Unfortunately, the rest is a mess of unidiomatic, system-specific, 10+ year old undocumented Ruby code.

My solution? Merge Stdlib and core into one big standard library and do what every other language does when things have to be brought in manually - specify it in the documentation. Exceedingly old libraries should be either adequately documented and brought up to spec with more modern Ruby or simply tossed out — the language has broken compatibility over the years, and so should the libraries.

5. Rails (and other massive Ruby projects)

This one is last not because it’s the least important (it isn’t) or because I had to rack my brains for things I dislike about Ruby (I didn’t), but because I think it’s the one that most people will disagree with me about. Call me manipulative, but I like to ease people into my unpopular software opinions.

Rails is a mess, and so are a lot of the other large projects known for being written in Ruby.

This wouldn’t be so bad (apart from the negative reputation, which isn’t a language problem) if it weren’t for the fact that these projects have an outsized influence on both the community and the direction of the Ruby language as a whole.

Unfortunately, they do. Looking a problem in Ruby up on the Web follows this rough chain of events:

  1. Google the problem
  2. Skip the first 2-3 results, which are usually blog posts about Rails
  3. Find the first StackOverflow question that looks remotely applicable
  4. Scroll down until you find a solution that doesn’t:
  5. Assume that you’re developing on Rails
  6. Assume that you want to use ActiveRecord (or any other Rails satellite)
  7. Claim that you’re suffering from the XY Problem and actually are developing on Rails
  8. Attempt to apply the solution, goto 1 if you fail

Rails is the dominant example, but Ruby often feels like a community divided.

On one hand are web developers intent on twisting the language to suit their (primarily web-based) needs, and on the other are administrators and non-web programmers (read: Perl refugees) intent on burrowing out their own little fifedoms in the Ruby ecosystem. The result is a community that can’t decide whether it’s centered around a framework that happens to run in a language, or the language itself.

This impedes language advancement (backwards compatibility!), and makes the Ruby community feel much smaller than it actually is. The “solution” is to simply grit your teeth and wait for Rails to decline in popularity. Even so, I couldn’t help but mention it here.

Concluding Notes

I came up with this list relatively quickly.

If you know or come up with something that you think is worse, feel free to drop it below and we’ll discuss it — my grievances probably don’t trump yours.

- William