19

When I do:

puts(nil or 4)

Ruby complains:

SyntaxError: syntax error, unexpected keyword_or, expecting ')'

Why is that? puts(nil || 4) does work, but I wonder why or doesn't. I thought the difference between the two was only in their operator precedence.

(I know the expression nil or 4 doesn't seem useful, as it always returns 4. It's just an example, for simplicity's sake. My actual expression is Integer(ENV['WD'] or 4).)

sawa
  • 165,429
  • 45
  • 277
  • 381
Niccolo M.
  • 3,363
  • 2
  • 22
  • 39

3 Answers3

15

Short answer

Because that's how ruby syntax is.

Longer answer

and/or keywords were designed to be used in control flow constructs. Consider this example:

def die(msg)
  puts "Exited with: #{msg}"
end

def do_something_with(arg)
  puts arg
end

do_something_with 'foo' or die 'unknown error'
# >> foo
# >> Exited with: unknown error

Here or works nicely with ruby's optional parentheses, because of ruby parsing rules (pseudo-BNF).

In short, an argument list (CALL_ARGS) is a list of ARG, separated by comma. Now, most anything is an ARG (class definitions, for example, through being a PRIMARY), but not an unadorned EXPR. If you surround an expression with parentheses, then it'll match a rule for "compound statement" and, therefore, will be a PRIMARY, which is an ARG. What this means is that

puts( (nil or 4) ) # will work, compound statement as first argument
puts (nil or 4)  # same as above, omitted optional method call parentheses
puts(nil or 4) # will not work, EXPR can't be an argument
puts nil or 4 # will work as `puts(nil) or 4`

You can read the grammar referenced above to understand exactly how it works.

BONUS: Example of class definition being a valid ARG

puts class Foo
       def bar
         puts "hello"
       end
     end, 'second argument'

# >> bar # this is the "value" of the class definition
# >> second argument
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • 3
    great explanation! – Andrey Deineko Jun 03 '16 at 12:09
  • @DaveSchweisguth: because they can be ARG. https://monosnap.com/file/wEA1jUDcU4CQ81Fuh7hkEcMgIf9K80.png. – Sergio Tulentsev Jun 03 '16 at 13:17
  • 2
    Ah, I see. `and` and `or` have the precedence they do because they're not explicitly defined to be ARG. So many operators are definited to be ARG that it's easy to think that ARG and EXPR are the same. Nasty. I think we'd have been better off without `and` and `or`. – Dave Schweisguth Jun 03 '16 at 13:28
  • 2
    @DaveSchweisguth: yes, exactly. I never use them. :) – Sergio Tulentsev Jun 03 '16 at 13:29
  • 2
    @DaveSchweisguth And the style guide recommends against them. https://github.com/bbatsov/ruby-style-guide#no-and-or-or – SteveTurczyn Jun 03 '16 at 13:44
  • I never use them either, simply because remembering details of operator precedence is annoying, but now I have a new reason to dislike them: their existence requires Ruby to have significantly more complicated parsing rules than it would without them. Or can they share the blame: is there other syntax that breaks the illusion that a method argument is a full-fledged expression? – Dave Schweisguth Jun 03 '16 at 14:06
  • @Sergio Tulentsev: thank you for the nice answer. I embarked on writing my own answer because the first version(s) of you answer left room for confusion, and I also wanted to know how exactly the BNF itself explains the `func (1 or 2)` thing. – Niccolo M. Jun 03 '16 at 14:29
  • @SteveTurczyn: Thanks for linking to the style guide. But the anchor in the link doesn't work. Could you please write here 3 or 4 words that we should search for in that long document to arrive at the relevant section? – Niccolo M. Jun 03 '16 at 14:30
  • @NiccoloM.: the anchor does work for me. The guide is long, give it a second to load. :) And thanks for the good self-answer! – Sergio Tulentsev Jun 03 '16 at 14:33
  • 1
    As for the style guide @SteveTurczyn linked to: for the benefit of users whose browsers aren't compatible with github's JavaScript anchors (I'm using a "old" Opera browser): you can search that long page for the sentence "The and and or keywords are banned" to arrive at the relevant section. – Niccolo M. Jun 03 '16 at 14:42
  • @NiccoloM. thanks for providing the search terms for anchor-incompatible browsers. – SteveTurczyn Jun 05 '16 at 18:11
11

It is because or and and have lower precedence than method call. Your expression is interpreted as:

{puts(nil} or {4)}

where {} stands for grouping. The syntax error comes from the expression

puts(nil

(and the following will also raise a syntax error):

4)

If you force grouping by putting a pair of parentheses around the expression, then it will work the way you intended:

puts((nil or 4))

Notice that the outer pair of parentheses is used for method call, not grouping, hence just having one pair of parentheses has no effect of changing the grouping.

Alternatively, if you disambiguate a single pair of parentheses to be used for grouping by putting a space, then that will work too:

puts (nil or 4)
sawa
  • 165,429
  • 45
  • 277
  • 381
7

@Sergio Tulentsev (and @sawa) gave a good answer, but I want to rephrase it so I can understand it quickly in the future:

Ruby lets us drop parenthesis in function calls. That is, instead of:

func1(ARG, ARG, ARG) or func2(ARG, ARG, ARG)

We can do:

func1 ARG, ARG, ARG or func2 ARG, ARG, ARG

However, in order to make this last line behave like the first one, "or" can't be an operator used in the top-level of an ARG (otherwise that last line will be interpreted as func1(ARG, ARG, ARG or func2 ARG, ARG, ARG)). Indeed, when we look in the BNF we see that ARG doesn't directly mention "or"/"and" (which means it's illegal there).

But ARG still makes it possible to use "or": by wrapping the expression in parentheses. In the BNF we see this as the PRIMARY alternative that ARG can branch to (as PRIMARY, in its turn, branches to '(' COMPSTMT ')').

Now, as to why func (1 or 2) and func((1 or 2)) work whereas func(1 or 2) doesn't:

  • func(1 or 2) is what the BNF calls FUNCTION, which expands to OPERATION ['(' [CALL_ARGS] ')'], which means the ARG is "1 or 2", but, as we've seen, ARG can't contain "or", so it's invalid.

  • func((1 or 2)) is, again, OPERATION ['(' [CALL_ARGS] ')'], but here the the ARG is "(1 or 2)", which is a valid ARG (see PRIMARY mentioned above).

  • func (1 or 2) is what the BNF calls COMMAND, which expands to OPERATION CALL_ARGS, which means the ARG is "(1 or 2)", which is a valid ARG (see PRIMARY mentioned above).

Niccolo M.
  • 3,363
  • 2
  • 22
  • 39