1

I understand the difference between || and or, or && and and, but what I don't understand is this:

Consider a method that returns the value of @var, or initializes it if @var is nil for false.

As I came from JavaScript background, I would write it like this, since || has higher precedence than =:

def some_method
  @var || (@var = MyClass.new)
end

or use or, since or has lower precedence:

def some_method
  @var or @var = MyClass.new
end

or more concisely:

def some_method
  @var ||= MyClass.new
end

But it so happened that this version also works:

def some_method
  @var || @var = MyClass.new
end

I search the web but didn't find useful results.

Most of them just tell the difference between || and or.

The operator precedence table clearly says that || has higher precedence than =.

I can even do a = b || c = d and Ruby sees it as a = (b || (c = d)).

Is this behavior documented somewhere or is it some kind of magic in Ruby?

P.S. CoffeeScript also has the same behavior.

Update / Clarification: This question is not about short-circuit evaluation. But about operator precedence. Please let me rephrase it again:

If || has higher precedence than =, then why does ruby sees a || a = b as a || (a = b), but not (a || a) = b and raise a syntax error?

Thai
  • 10,746
  • 2
  • 45
  • 57

3 Answers3

3

Ruby uses what is called short-circuit evaluation to evaluate logic expressions like these. The second argument will only be considered when the first is insufficient to evaluate the expression.

In your example, since @var is initially nil, the latter half of the expression is evaluated and set to an instance of MyClass.new.

The expansion of those expressions is pretty interesting. There are a few blog posts here and here that cover it quite nicely.

Zajn
  • 4,078
  • 24
  • 39
  • 1
    The links you gave covered just about how `a ||= b` expands to `(a || a = b)`, but not why `||` suddenly has lower precedence than `=` when it as at the left of `=`. But these links are an interesting read. Thanks for sharing. – Thai Feb 03 '13 at 17:39
2

Precedence becomes an issue only when there is ambiguity in the expression. In case of:

 @var || @var = MyClass.new

there is no ambiguity. The assignment operator = makes sense only if there is a single variable on the left side of it (disregarding the complexities that arise from multiple assignment). It does not make sense to assign something to a variable called "@var || @var". Therefore, there is no ambiguity. The only way to interpret the above expression is to interpret it as:

 @var || (@var = MyClass.new)

Therefore, precedence is not an issue here.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • 3
    Or so one would guess. Could you back your words by pointers to rubyspec or MRI parser code? – Sergio Tulentsev Feb 03 '13 at 17:35
  • +1, this answer makes most sense to me so far. I totally forget about the ambiguity, as most languages that I've used to (except Ruby) doesn't do anything like that. An example is JavaScript, which also has short-circuit evaluation. It just sees `a || a = 5` as `(a || a) = 5` and raises a SyntaxError. However I'm looking to see where it is documented, and trying to find the grammar that made this possible in Ruby. – Thai Feb 03 '13 at 18:00
  • Ok, I'd say this is an accepted answer. Looking at the (unofficial) grammars of Ruby, I saw that operators above the `=` operator lives in the same rule, which clearly made something like `a + a = 3` a valid grammar (and maybe some precedence may kick in later, although I couldn't find solid proof for this), unlike JavaScript which has top-down operator precedence, and thus make such an expression impossible. – Thai Feb 03 '13 at 18:25
  • 1
    `a || a = 5` doesn't read as `(a || a) = 5`. The precedence is irrelevant since `||` is left association while `=` is right. This parsing is a natural consequence of using a LALR parser. – matyr Feb 04 '13 at 05:39
  • 1
    @matyr Association matters only when the competing operators have the same precedence. Since `||` and `=` do not, association is irrelevant here. – sawa Feb 04 '13 at 06:06
  • 1
    Right, I brainfurted there. As answered `a || a` cannot be the left of `=`. The parser shifts more and resolves `a = 5` as the right of `||`. – matyr Feb 04 '13 at 06:54
0

I would forget about and and or completely. Just use && and ||, with parenthesis if you need them.

Your last example only works because you're using an instance variable. Consider the difference here:

1.9.3> newvar
NameError: undefined local variable or method `newvar' for main:Object
        from (irb):59
        from /home/don/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
1.9.3> @newvar
 => nil

If you try newvar || newvar = 3 it will raise an error.

You might want to look at this SO question: What does ||= (or-equals) mean in Ruby?.

Community
  • 1
  • 1
ohspite
  • 1,269
  • 1
  • 10
  • 18
  • Sorry, that did not answer my question. Try initializing `newvar` to nil and then `puts newvar || newvar = 42`. It has the same effect as `puts newvar = newvar || 42`. Please see the clarification I added to this question. – Thai Feb 03 '13 at 17:27
  • Okay, the answer to your question in the clarification is short-circuiting, like others have said. I'm just saying, the only reason your last version worked was because you weren't using undefined variables. `||=` does not behave the same way as `+=` and others, because it handles the case of undefined variables gracefully. – ohspite Feb 03 '13 at 17:46