Question

The reference I'm using for Ruby operators is http://phrogz.net/programmingruby/language.html#table_18.4 According to this reference, and others I've seen, equality operators take precedence over logical AND (&&, not Ruby's and).

I have the following:

foo = nil
foo < 5 # NoMethodError: undefined method `<' for nil:NilClass

To check for foo we do:

foo && (foo < 5) # Note the parenthesis

But this works:

foo && foo < 5 # why does this work?

Because of operator precedence, foo < 5 should happen first, resulting in an error before the AND can even be evaluated. Am I missing something?

Was it helpful?

Solution

TL;DR

< has higher precedence than &&, which affects how Ripper tokenizes your expressions. Evaluation happens after tokenizing.

Booleans and S-Expressions

In your example, foo && foo < 5 is tokenized into two expressions:

require 'ripper'
Ripper.sexp 'foo && foo < 5'
#=> [:program,
# [[:binary,
#   [:vcall, [:@ident, "foo", [1, 0]]],
#   :"&&",
#   [:binary, [:vcall, [:@ident, "foo", [1, 7]]], :<, [:@int, "5", [1, 13]]]]]]

So, the parser sees this as functionally equivalent to (foo) && (foo < 5) because < has higher precedence than &&. However, since Ruby uses short-circuit evaluation for Boolean operations, it never evaluates the right-hand side of your Boolean expression unless foo evaluates to true.

OTHER TIPS

The following helped me a bit to understand the issue:

I can rewrite

foo && foo < 5

as

foo && foo.<(5)

Which now makes much more sense. You would expect the same behavior from a statement like:

foo && foo.even?

You would expect first foo to be evaluated and only then foo.even?. The same is here when foo gets evaluated before foo.<(5) or as you can write it foo < 5.

Now I'm trying to come up with an example where && and < really behave according to the precedence table, but still without success.

&& and || evaluate operators from left to right. and the evaluation stops as soon as the truth or falsity of the statement is known. foo < 5 will never get evaluated. This is why the second approach works.

Also, && and || have much higher precedence. They are NOT the same as and and or.

The precedence is about binding, not necessarily order of operations. If the LHS of the && evaluates to false then it won't bother evaluating the RHS at all.

Ruby is intended to be kind of functional, so I'll give you some Haskell to show this in more detail:

true && x = x
false && x = false

Since the left hand side is false, it will never go on to calculate the right hand side, so it will crash. This is some very simply lazy evaluation. On the second line, the value of x is irrelevant, so it never bothers calculating it as it doesn't need to. This is the beauty of lazy evaluation. :)

The && short circuits, if foo is nil or false, the right hand side will not be evaluated.

The parser of course will do what it will do as described below. But the evaluation happens after that step.

Using parsergem to examine the code, we can get some hints as to what the Ruby parser is doing.

I have these two files:

foo1.rb

foo = nil
foo && foo < 5

The AST representation:

(begin
  (lvasgn :foo
    (nil))
  (and
    (lvar :foo)
    (send
      (lvar :foo) :<
      (int 5))))

foo2.rb

foo = nil
foo && (foo < 5)

The AST representation:

(begin
  (lvasgn :foo
    (nil))
  (and
    (lvar :foo)
    (begin
      (send
        (lvar :foo) :<
        (int 5)))))

I don't know if that clarifies or confuses.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top