Reputation: 221
I'm new to Ruby, and trying to understand why this works (it does seem to, at least according to "the Master"):
def array_of_fixnums?(array)
true unless array.find { |item| item.class != Fixnum }
end
My concern is where the "false-ness" is coming from when the array contains non-fixnum values. Am I right to assume there is no implicit "else false
" in the unless
statement? In that case I assume it must be coming from the nil
value returned by Enumerable#find
. Is that correct?
If so, that seems a bit shaky. Might it be better to return false
explicitly, like this?
array.find { |item| item.class != Fixnum } ? false : true
Is there another, better way entirely? Thanks for helping me wrap my head around this, and for any "best practice" suggestions.
Upvotes: 2
Views: 1431
Reputation: 110725
Let's look at why true unless condition
works as it does.
I believe x unless condition
was provided as an esthetic improvement to x if !condition
, but the two are equivalent (and, in my opinion, it is an improvement). To get rid of double-negatives, I'll just consider x if condition
.
One of Ruby's basic tenets is that every statement must return a value (namely, the last expression evaluated). Therefore, x if condition
must be evaluated as if condition then x else y end
. But what is y
? If y
evaluated as true
or false
, x if condition
would be meaningless when x
is a boolean expression. To see why, consider:
w = (x == 5 if z == 6)
If w
were true, we could conclude that x = 5
and z = 6
, but if w
were false, we wouldn't know whether z = 6
and x != 5
, or z != 6
and x = 5
or x != 5
. Considering that any value of y
other than nil
is evaluated as true
or false
, the only reasonable value for y
is nil
. That way, if w == nil
is be available. Of course, this is still a problem (in other situations) when x
could possibly be nil
.
I would welcome clarification and elaboration. Perhaps there is also a less convoluted way of making this argument.
Upvotes: 0
Reputation: 34072
Your method is returning nil
not because find
returns nil
, but because if your inline conditional does not pass, the method has no explicit return value. This would be more clear if it were not inline, consider:
def array_of_fixnums?(array)
unless array.find { |item| item.class != Fixnum }
return true
end
# otherwise don't explicitly return (implicit nil)
end
While relying on the falsiness of nil
will often work, it is problematic in that it does not follow the principle of least surprise. A ?
method should return true or false.
But your method has worse problems. It uses confusing logic (a double negative), and itself relies on the falsiness of nil
and the truthiness of not nil
, to function. Consider what happens if your method were passed [false]
. Oops.
The better way would be something like:
array.all? {|n| n.is_a? Fixnum }
The reasoning is that this method does exactly what it says, plainly.
Returning a boolean explicitly, while not necessarily wrong, is superfluous and often considered bad practice. Rather consider the example, which says, in ruby speak, is every one of the values in this array a Fixnum?. The result of that expression is what the method is after; there's no reason to evaluate it then return true|false
.
Upvotes: 4
Reputation: 13921
From the find method doc:
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
Thus, all you need is:
def array_of_fixnums?(array)
array.find { |item| item.class != Fixnum }
end
If anything is found, that will be returned, otherwise nil will be returned, and the method will evaluate do false.
However, as the item return could be an false
or nil
(if any item in the list is false or nil) I would recommend that you use the .any?
method instead.
def array_of_fixnums?(array)
array.any? { |item| item.class != Fixnum }
end
Upvotes: 2