4

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)?

sawa
  • 165,429
  • 45
  • 277
  • 381
Caroline
  • 59
  • 1
  • 4
  • 2
    `true or (true and false)` versus `(true or true) and false` – Hunter McMillen Aug 23 '18 at 18:38
  • I noticed this with "puts true or true and false" in irb. I got "true" and right underneathe it, "=> false" – Caroline Aug 23 '18 at 18:38
  • Why would "puts" make "and" precedent over "or" while an if statement did not? – Caroline Aug 23 '18 at 18:41
  • precedence for `puts` and `if` tokens are different. Meaning that `puts true or true and false` is getting parsed into `(puts true) or true and false`. The `if` token likely binds less tightly. This results in puts being called with true and the return false evaluating to false – Hunter McMillen Aug 23 '18 at 18:43
  • 1
    [Ruby precedence](https://ruby-doc.org/core-2.2.0/doc/syntax/precedence_rdoc.html) – engineersmnky Aug 23 '18 at 18:58
  • 1
    I think this is a good question because operator precedence and lexical bindings are not the most intuitive aspects of any language, and Ruby's expressiveness can make it even trickier. You can't look them up unless you know you need to look them up, and even then I think the results can have surprising edge cases. [See below](https://stackoverflow.com/a/51993732/1301972) for a concrete example from the parser. – Todd A. Jacobs Aug 23 '18 at 20:42
  • 1
    Quantum Ruby! – iGian Aug 23 '18 at 22:01
  • It also occurs to me that there may be some confusion around output and return values, versus Boolean or expression evaluation. The OP is doing a good job of explaining her mental map of the problem, but the solution for the OP may be more basic than the precedence explanations posted so far. Just my $0.02 based on various comments. – Todd A. Jacobs Aug 23 '18 at 22:07
  • `and` and `or` are _control flow_ operators, i.e. `do_this or fail` or `do_that and return`. They are not a replacement for `&&` and `||`. Trying to use them that way can result in rather unexpected behavior. See http://www.virtuouscode.com/2010/08/02/using-and-and-or-in-ruby/ for some examples. – Stefan Aug 24 '18 at 07:20

4 Answers4

7

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.

mwp
  • 8,217
  • 20
  • 26
  • This is __the__ reason. :) – Sergio Tulentsev Aug 23 '18 at 18:50
  • Please note that `puts true || true && false` outputs `true` and returns `nil` in irb whereas `puts true or true and false` outputs `true` and returns `false`. `&&` takes precedence over `||` which takes precedence over `and` and `or` (which have same precedence) – Caroline Aug 23 '18 at 20:33
  • 5
    Your first sentence is in error, which is understandable since the Boolean statement is horribly ambiguous. Ruby actually evaluates it closer to `(puts(true) or true) and (false)`, per [the S-tree](https://stackoverflow.com/a/51993732/1301972) returned by Ripper. Reducing ambiguity through higher-precedence operators and parenthesizing is generally good advice, though. – Todd A. Jacobs Aug 23 '18 at 20:34
  • @ToddA.Jacobs Ah, interesting. Yes, that truly is a nightmare of an expression. – mwp Aug 23 '18 at 20:36
  • @Caroline they are not exactly comparable you are essentially comparing `2 + 7 * 2` to `(2 + 7) * 2` that's just how precedence works – engineersmnky Aug 23 '18 at 21:12
  • 1
    @Caroline Kernel#puts never returns false; it always returns nil or raises an exception (e.g. if standard output is closed). However, almost everything in Ruby is an expression. puts true || true && false or puts true or true and false return false because that's the last expression evaluated by the parser, not because puts is evaluating to (or returning) true or false. – Todd A. Jacobs Aug 23 '18 at 22:04
  • puts false || true and false outputs true and returns nil in irb. What order should I evaluate that statement? Also, puts false or true && true outputs false and returns true in irb. How do I bracket that? Thanks – Caroline Aug 23 '18 at 23:50
4

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.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
2

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.

Joseph Cho
  • 4,033
  • 4
  • 26
  • 33
  • But we can expand your excellent examples further. If we write a = true or false, puts a returns true. If we write a = true and false, puts a returns true. However, in a = false and true, puts a returns false. Why is that? If we use && and ||, it doesn't matter whether true or false came first. If we use and/or, a becomes the first boolean. – Caroline Aug 24 '18 at 00:03
  • That's because `&&` and `||` trump the `=` sign. `and` and `or` rank below `=`. So in the last example `a = false and true` will set `a = false` first. – Joseph Cho Aug 24 '18 at 01:48
  • I admit the other answers here are more in depth. But answers that are too wordy can make things more confusing. – Joseph Cho Aug 24 '18 at 01:49
1

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

  1. &&

  2. ||

  3. puts

  4. and, or

    puts false or true && true becomes puts false or true. The first part, puts false outputs false and returns nil. The statement is now nil or true which returns true

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.

Community
  • 1
  • 1
Caroline
  • 59
  • 1
  • 4