问题
I get the following:
puts true or true and false
# >> true
whereas I also get:
if true or true and false
puts "that's true!"
else
puts "that's false!"
end
# >> that's false!
Why is true or true and false
both true
and false
(like Schrödinger's cat)?
回答1:
It has to do with precedence. puts true or true and false
actually evaluates as (puts true) or (true and false)
[EDIT: Not exactly. See the note from Todd below.], and if true or true and false
evaluates as if (true or (true and false))
. This is due to the precedences of puts
(a method) and if
(a language keyword) relative to the other terms of the expression.
You see => false
in irb when you evaluate puts true or true and false
(remember, that's (puts true) or (true and false)
) because puts
outputs true
and returns nil
, which is falsey, causing the (true and false)
to be evaluated next, which returns false
.
This is one reason why most Ruby guides recommend using &&
and ||
instead of and
and or
in boolean expressions. puts true || true && false
evaluates as puts (true || (true && false))
and if true || true && false
evaluates as if (true || (true && false))
, both as you'd expect them to.
回答2:
Precedence
Ruby's parser relies on a precedence table. In your example, or
has higher precedence than and
. Additionally, short-circuit evaluation won't evaluate the second term of an or-condition if the first expression is truthy.
Also, note that in Ruby Kernel#puts is a method that takes optional arguments, while if
is a lexical token. While many people omit parentheses in idiomatic Ruby, precedence can change what the parser sees when it evaluates a complex or ambiguous expression like true or true and false
. As you will see below, the distinction between a conditional and a method complicates matters further.
In general, one should parenthesize expressions to avoid ambiguity, and rely on operator precedence as little as possible. There are always exceptions, especially in expressive languages like Ruby, but as a rule of thumb it can simplify a Rubyist's life immensely.
Examine the Parser
If you are ever in doubt about what the parser sees, you don't have to rely on reasoning alone. You can use the Ripper module to examine the symbolic expression tree. For example:
require 'pp'
require 'ripper'
pp Ripper.sexp 'true or true and false'
This will show you:
[:program, [[:binary, [:binary, [:var_ref, [:@kw, "true", [1, 0]]], :or, [:var_ref, [:@kw, "true", [1, 8]]]], :and, [:var_ref, [:@kw, "false", [1, 17]]]]]]
This shows that the parser thinks the expression, on its own, evaluates as if you'd parenthesized it as (true or true) and false
.
Likewise, an if-statement has the same precedence applied:
pp Ripper.sexp 'if true or true and false; end'
[:program, [[:if, [:binary, [:binary, [:var_ref, [:@kw, "true", [1, 3]]], :or, [:var_ref, [:@kw, "true", [1, 11]]]], :and, [:var_ref, [:@kw, "false", [1, 20]]]], [[:void_stmt]], nil]]]
However, because puts
is a method, it is parsed differently:
pp Ripper.sexp 'puts true or true and false'
[:program, [[:binary, [:binary, [:command, [:@ident, "puts", [1, 0]], [:args_add_block, [[:var_ref, [:@kw, "true", [1, 5]]]], false]], :or, [:var_ref, [:@kw, "true", [1, 13]]]], :and, [:var_ref, [:@kw, "false", [1, 22]]]]]]
In other words, the parser assumes your ambiguous statement is roughly equivalent to the following parenthesized expressions: (puts(true) or true) and (false)
. In this case, the first true
was assumed to be an argument to Kernel#puts. Since the puts method always returns nil
(which is falsey), the second true
is then evaluated, making puts(true) or true
truthy. Next, the terminal expression is evaluated and returns false
, regardless of the fact that the puts-statement prints true
to standard output.
回答3:
There are two things going on here.
Evaluation
if true or true and false
puts "that's true!"
else
puts "that's false!"
end
true or true and false
evaluates to false. That's why that's false!
outputs.
Precedence
or
has lower priority than ||
. This can create confusing situations because the value being set, isn't the same as what's being evaluated. For example:
a = false || true
=> true
puts a
true
a = false or true
=> true
puts a
false
In the second example the evaluation is true
, but precedence sets a to false
. I hope this helps, I found this resource very helpful.
回答4:
This is my 2nd attempt to summarize my understanding from everyone's great response to my quesiton. A special shoutout to engineersmnky and Joseph Cho. The light bulb went on after reading your answers a couple of times.
puts false or true && true
outputs false
and returns true
puts false || true and false
outputs true
and returns nil
In this present case, the order of precedence is
- &&
- ||
- puts
and, or
puts false or true && true
becomesputs false or true
. The first part,puts false
outputsfalse
and returnsnil
. The statement is nownil or true
which returnstrue
Similarly, puts false || true and false
becomes puts true and false
. Then the first part puts true
outputs true
and returns nil
. The statements is now nil and false
which will return nil
.
来源:https://stackoverflow.com/questions/51992245/why-does-true-or-true-and-false-appear-to-be-simultaneously-true-and-false