Yesterday, a simple pull request to Rails core dramatically exploded over the issue of what a predicate should return.

TL;DR

This tweet pretty much sums it up:

For what it’s worth I’d consider any ruby code that relies on true/false singletons from predicates to be in error.

@avdi

Background

To really appreciate the dicussion, we need to know two things about Ruby.

What is true?

Ruby, like many programming languages, has a specific understanding of true or false that goes beyond expressing the concept in a boolean type. Ruby understands the concept of whether a value is true or false, and therefore any expression requiring a boolean value (for example, using the boolean logic operators), without needing an explicit set of boolean values. Ruby does have the keywords true and false, sometimes called the true singleton and the false singleton since the values are shared in the Ruby VM, which are Ruby’s only notion of anything like a boolean type.

Instead, in Ruby, when a boolean value is required in boolean logic expressions (or conditionals), Ruby is really only concered with the truthyness of the value. Ruby evaluates a value as either truthy or falsey with a simple rule.

All of the following are falsey values.

And anything else is a truthy value:

This is one of Ruby’s many affordances for developers (and not the only one at play in this drama). It allows us to write concise and expressive code like this:

1might_be_initialized_already ||= {}

To be very clear about what this is doing, lets rewrite it naively:

1unless might_be_initialized_already
2  might_be_initialized_already = {}
3end

Note that we do not care what the type of might_be_initialized_already is, we can use the incredibly useful definition of truthy and falsey to evaluate the conditional without coercion or other type-based manipulations.

What is a predicate?

Ruby has, among its many affordances for developers (and the second one at play in this drama), the optional method name suffixes ! and ?.

At this point, you might be thinking, “Hey what about =, it is also a suffix!” and you would be almost right. = is a suffix, but unlike ! and ? it actually means something to Ruby, namely that expressions like our earlier example:

1self.properties ||= {}

are understood correctly. ! and ? mean nothing to Ruby, they exist to make our code just a bit more pleasant to read. ! and ? are affordances that we use by convention. There is no requirement to use them, yet we do so all the time. And yes, = is another one of those affordances for the developer.

The ! suffix indicates that the method modifies the receiver, for example:

1def reset!
2  # reset everything back to its initial state
3end

The convention for ! is summed up in Programming Ruby by Thomas1:

Methods that are “dangerous,” or modify the receiver, may be named with a trailing !.

The ? suffix indicates that the method is a predicate, returning a truthy or falsey value. For example:

1reset! unless valid?

The convention for ? is also summed up in Programming Ruby by Thomas:

Methods that act as queries are often named with a trailing ?, such as instance_of?.

The astute reader will have noted that by definition every method in ruby returns a truthy or falsey value. And in The Ruby Programming Language by Flannagan and Matz (yes, that Matz), it is noted as part of the convention for ?:

Predicates typically return one of the Boolean values true or false, but this is not required, as any value other than false or nil works like true when a Boolean value is required. (The Numeric method nonzero?, for example, returns nil if the number it is invoked on is zero, and just returns the number otherwise.)

The drama

The pull request in question reverted year-old Rails code, changing it to return true or false instead of just returning a truthy or falsey value. The instigation of the request was that originally the xml_http_request? method returned true or false and existing code had used the predicate as a RHS. When the value constraint was removed, all the code using the predicate as a RHS caused errors (the values were no longer true and false).

My opinion

Since a predicate method is only a method with a ‘?’ appended to the name, the convention around its use covers two questions, the obvious one:

What values does the predicate method return?

We might say that the convention is that the predicate method constrains its return value to true and false.

Or we might say that the convention is that the predicate method does not constrain its value.

Given the explicit endorsement, the second is clearly the convention. This also matches the experience of the Ruby community to date.

As an aside, following the precedent of =, one can easily imagine a modified Ruby that would make predicate methods returning only true and false more than just a convention and simply the way Ruby works.2

And the not so obvious one:

When do you use the predicate method?

If we accept that the predicate method constrains its return value, then accepting that the predicate method is usable anywhere makes sense. In this case using a predicate as a RHS is perfect when you want to get a true or false value.

If we accept that the predicate method does not constrain its return value, then accepting that the predicate method is usable anywhere does not make sense. In this case using a predicate as a RHS fails miserably, as you get whatever happens to be returned when determining its truthyness.

This is really the cause of the dust-up in Rails core. Whatever the original intentions were, at one point the predicate method in question returned true or false. Depending on a predicate method to return a true or false for use in a RHS is not really good Ruby practice. There is no guarantee that the values are anything other than truthy or falsey, which is to say, anything at all.

1 The Pickaxe book.

2 Just in case someone misreads that sentence, Ruby does not work this way.

blog comments powered by Disqus