In PHP, this evaluates to true
:
$a = 1
$b = 2
var_dump($a && $b); // true
In ruby, this evaluates to 2
:
a = 1
b = 2
p a && b # 2
Why does ruby return the value of the last statement (when the first is true and the second one is also true) and does not return a boolean?
I have two arrays and I iterate them with an external iterator:
a = [1,2,3].to_enum
b = [5,6,7].to_enum
c = []
begin
while a_next = a.next && b_next = b.next
result = a_next + b_next
p "a[x] + b[x] = c[x] = #{a_next} + #{b_next} = #{result}"
c << result
end
rescue
p c
end
The condition: while a_next = a.next && b_next = b.next
sets a_next = b_next
(the first question seems related to this behavior, I think).
But when I wrap the two &&
operands, it works as expected: (a_next = a.next) && (b_next = b.next)
.
There's several aspects here.
Why does ruby returns the value of the last statement (when the first is true and the second one is also true) and does not return a boolean?
Because it is more useful this way. This semantics is in fact so useful that PHP 7 added it (but as a new operator, ??
). In Ruby, just as in PHP, all values are truthy or falsy. Unlike PHP, Ruby has a much stricter idea about it: only false
and nil
are falsy, everything else is truthy. This allows you to easily give defaults:
name = options[:name] || "John Doe"
If options[:name]
is not found and returns nil
, then that part is falsy and the right side of ||
will be returned; otherwise, options[:name]
will be returned.
In most cases you don't need a boolean, because truthiness or falsiness suffices. If you really really want to have a boolean, for example in order not to leak private information out of a class, the idiom !!value
is common:
def has_name?
!!(self.name || self.nickname)
end
The result of !
(negation) is always boolean; if you negate twice, you will convert truthiness to true
and falsiness to false
.
Finally,
The thing that bugs me is the condition in the
while
-while a_next = a.next && b_next = b.next
- written like this, it always setsa_next = b_next
(the first question seems related to this behavior, I think). But when i wrap the two&&
operands - it works as expected -(a_next = a.next) && (b_next = b.next) # works ok
.
Then you need to wrap them. That's due to operator precedence, and works like this by design, because it is more normal to write
blue_green = colour == :blue || colour == :green
than
blue_green = (colour == :blue || colour == :green)
There is another set of boolean operators that are actually designed to work like you propose, the only difference being the precedence, so you could write this and have it work:
while a_next = a.next and b_next = b.next
It is identical to
while (a_next = a.next) && (b_next = b.next)
A warning though: using and
and or
operators instead of &&
and ||
improperly is a common enough mistake that many style guides outright ban them (they are useful only in this context - assignment inside loop conditions - and it can be solved with parentheses instead). E.g.:
The
and
andor
keywords are banned. It's just not worth it. Always use&&
and||
instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With