Книга: Metaprogramming Ruby 2
Назад: Mimic Methods
Дальше: Self Yield

Nil Guards

Most Ruby beginners looking through someone else’s code are perplexed by this exotic idiom:

 
a ||= []

In this example, the value to the right happens to be an empty array, but it could be any assignable value. The ||= is actually a syntax shortcut for the following:

 
a || (a = [])

To understand this code, you need to understand the details of the “or” operator (||). Superficially, the || operator simply returns true if either of the two operands is true—but there is some subtlety to this. Here’s the way that || actually works.

Remember that in a Boolean operation, any value is considered true with the exception of nil and false. If the first operand is true, then || simply returns the first operand, and the second operand is never evaluated. If the first operand is not true, then || evaluates and returns the second operand instead. This means the result will be true unless both operands are false, which is consistent with the intuitive notion of an or operator.

Now you can see that the previous code has the same effect as this:

 
if​ ​defined?​(a) && a
 
a
 
else
 
a = []
 
end

You can translate this code as this: “If a is nil, or false, or hasn’t even been defined yet, then make it an empty array and give me its value; if it’s anything else, just give me its value.” In such cases, experienced Ruby coders generally consider the ||= operator more elegant and readable than an if. You’re not limited to arrays, so you can use the same idiom to initialize just about anything. This idiom is sometimes called a Spell: , because it’s used to make sure that a variable is not nil.

Nil Guards are also used quite often to initialize instance variables. Look at this class:

 
class​ C
 
def​ initialize
 
@a = []
 
end
 
 
def​ elements
 
@a
 
end
 
end

By using a Nil Guard, you can rewrite the same code more succinctly:

 
class​ C
 
def​ elements
 
@a ||= []
 
end
 
end

The previous code initializes the instance variable at the latest possible moment, when it’s actually accessed. This idiom is called a Spell: . Sometimes, as in the earlier example, you manage to replace the whole initialize method with one or more Lazy Instance Variables.

Nil Guards and Boolean Values

Nil Guards have one quirk that is worth mentioning: they don’t work well with Boolean values. Here is an example:

 
def​ calculate_initial_value
 
puts ​"called: calculate_initial_value"
 
false
 
end
 
 
b = nil
 
2.times ​do
 
b ||= calculate_initial_value
 
end
<= 
called: calculate_initial_value
 
called: calculate_initial_value

The Nil Guard in the code above doesn’t seem to work—calculate_initial_value is called twice, instead of once as you might expect. To see where the problem is, let’s write the if equivalent of that Nil Guard.

 
if​ ​defined?​(b) && b
 
# if b is already defined and neither nil nor false:
 
b
 
else
 
# if b is undefined, nil or false:
 
b = calculate_initial_value
 
end

If you look at this if-based translation of a Nil Guard, you will see that Nil Guards are unable to distinguish false from nil. In our previous example, b is false, so the Nil Guard reinitializes it every time.

This little wrinkle of Nil Guards usually goes unnoticed, but it can also cause the occasional hard-to-spot bug. For this reason, you shouldn’t use Nil Guards to initialize variables that can have false (or nil, for that matter) as a legitimate value.

Назад: Mimic Methods
Дальше: Self Yield