In Progress
Unit 1, Lesson 21
In Progress

Intention Revealing Argument

Video transcript & code

Pop quiz: in this code, what does the true passed as the argument to #transaction mean?

require 'pstore'

store = PStore.new('data.pstore')
store.transaction(true) do
  # ...
end

If you happen to be very familiar with Ruby's PStore library, you might know that this boolean argument is a flag indicating whether to start a read-only instead of a read/write transaction. Otherwise, it's not at all clear what true means in this context. In fact, even though I am pretty familiar with this library, I still have to look up the meaning of that argument every time, because I can never remember if true means read-only, or read-write.

If I controlled the PStore library I'd rewrite this method to accept an options hash (or keyword arguments, in Ruby 2.0), so that we could call #transaction it like this:

require 'pstore'

store = PStore.new('data.pstore')
store.transaction(read_only: true) do
  # ...
end

But even though we don't control the library, we can clarify this message send by introducing a variable whose sole purpose is to reveal our intent. To do this we define a variable named read_only, assign it the value true, and then pass that variable to store.transaction.

require 'pstore'

store = PStore.new('data.pstore')
read_only = true
store.transaction(read_only) do
  # ...
end

I find the addition of a line of code here very worth it for the increase in readability.

There's a alternative technique to this one that I've seen advocated in a few places. Instead of a local variable, we use a symbol in place of a true value. Here, we'll pass the symbol :read_only to store.transaction. Since symbols are always "truthy", it works just as well as passing a literal true value.

require 'pstore'

store = PStore.new('data.pstore')
store.transaction(:read_only) do
  # ...
end

I have some reservations about this technique. First of all, I find it a little bit misleading. If I was unfamiliar with PStore and I came across this code, I'd probably assume that the symbol :read_only was a specific special flag that the method recognized. If I wanted to learn more about it, I'd probably try and look up that symbol… and I'd hit a dead end, because there's actually nothing special about the choice of the symbol :read_only other than readability. We could have used the symbol :chunky_bacon and it would have worked just as well.

Second, it's only applicable to boolean arguments, and even then it works better for the true case than for the false case. Consider the Class#instance_methods method that lists a class's instance methods. It takes an optional boolean argument indicating whether to include methods defined on ancestor classes.

Array.instance_methods(true).count # => 167
Array.instance_methods(false).count # => 88

Let's say we wanted to use the symbol-as-boolean technique to turn off inclusion of superclass methods. A symbol is always true, so in order to use a symbol to pass false we have to negate it.

Array.instance_methods(!:include_super).count # => 88

I don't know about you but I don't find this bang-colon business terribly readable. Whereas with the intention-revealing argument, it's just as clear as when passing a true flag.

include_super = false
Array.instance_methods(include_super).count # => 88

There is one variation on this idiom that is worth talking about. If we object to the added line of code, we can actually use an inline variable assignment inside the argument list to achieve the same effect.

Array.instance_methods(include_super = false).count # => 88

This has a couple of things to recommend it. It's more concise, obviously. Not only that, but it actually gives the appearance of a keyword argument.

And this last point is actually why I shy away from using it. I've seen programmers who were newer to Ruby get confused by constructs like this because they believed they were actually dealing with keyword arguments. There's also the fact that some development tools may flag this as an "unused variable". So while I don't feel strongly about it one way or the other, I lean towards using the multi-line version.

That's all for today. Happy hacking!

Responses