Relative Sanity

a journal

Idiomatically speaking

This kind of thing makes me twitch:

def sold_widgets
  sold_widgets = []
  for widget in @widgets
    sold_widgets << widget if widget.sold?
  end
  sold_widgets
end

If you’ve been a Ruby programmer for long, you’ll be twitching too. How many things you’re twitching at (there’s a lot) probably determines how long you’ve been coding Ruby, and roughly how much of the Ruby Way kool-aid you’ve consumed.

Today, I want to look at a couple of things. The first that most people will spot (since it’s one of the first things most Rubyists seem to pick up) is the use of a for loop. No no no no no, we say. We know our Enumerable mix-in, so let’s use an iterator and pass it a block:

def sold_widgets
  sold_widgets = []
  @widgets.each do |widget|
    sold_widgets << widget if widget.sold?
  end
  sold_widgets
end

Now, that looks a bit better, but we haven’t really changed much. Yes, it’s more “idiomatic”, but visually, we really only changed the syntax used to extract the widget from our widgets. Enumerable lets us do better.

If you’re like me, the whole “set up container variable, populate container variable, return container variable” has always felt very “un-Ruby”. In this case, we can dispense with the local sold_widgets variable altogether by using another Enumerator method:

def sold_widgets
  @widgets.select do |widget|
    widget.sold?
  end
end

To anyone calling sold_widgets now, nothing has changed, but internally we’ve got rid of a lot of the boilerplate, as well as the hanging, implicit return of the local sold_widgets—which always feels like a hint that we’re doing something un-idiomatic.

Now this is great: we’ve dropped our five liner down to what could readably be turned into a one-liner. If we don’t mind using the symbol-to-proc pattern, we can even drop it to this:

def sold_widgets
  @widgets.select &:sold?
end

which I’d argue loses none of the clarity of what we are doing. If anything, it heightens it.

Now, let’s consider something similar but different:

def description
  description = "This is a widget. "
  description << “It has already been sold. “ if sold?
  description << “It is a limited edition. “ if limited?
  description
end

This gives me a similar sense of twitchiness. We’re not iterating this time, but we still have the “set up container variable, populate container variable, return container variable” pattern. The repetition and the implicit return at the end are the big smells here.

Wonderfully, Ruby 1.9 has our back here: though we’re not mixing in Enumerable, we do have an all-purpose way to work with any arbitrary Ruby object “in place”, just like using Enumerable#each, Enumerable#select, Enumerable#collect etc.

Enter Object#tap:

def description
  "This is a widget. ".tap do |desc|
    desc << “It has already been sold. “ if sold?
    desc << “It is a limited edition. “ if limited?
  end
end

No, it doesn’t reduce the number of lines of code, but it does drop our method to a single expression, the result of which is returned directly (just like our select example above).

tap allows any Ruby object to accept a block, with itself (not a reference) passed into the block for manipulation before returning itself. It’s amazingly handy for chaining modifications, or for performing conditional manipulations on an object before returning it. It allows us to (mostly) abandon the “container, populate, return” pattern in favour for a much more Ruby-ish “return manipulated object” pattern.

In fact, this sort of method chaining is an example of a Fluent Interface, which sounds like a very Ruby-ish pattern to me.

Now I probably overuse tap. I've seen it cause any number of seasoned Rubyists to scratch their heads when they first see it, which probably means it can make code harder to change in some teams (remember: house style trumps “idiom” every single time). This isn’t intended as some militant call to arms.

However, I do feel like we were in the same position with for…in many years ago: the arguments of “readability” and “intuition” back then were more about “familiarity” than anything intrinsicly better in the syntax. Personally, I find that tap feels more like Ruby, and given Ruby’s propensity for optimising for programmer happiness, I think the “feel” is a valid argument for its usage.

That said, I suspect using tap makes me a little too happy sometimes. I should really get out more.


Love tap? Hate it? Think my examples are dumb? Shout at me on twitter.