Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Precedence of to-block unary `&`

Consider the following Ruby code:

[1,3].any? &:even? || true
# => false
[1,3].any? &nil || :even?
# => false
[1,3].any? &nil || :odd?
# => true

So it seems that Boolean-or || has higher precedence than to-proc unary &. I didn't expect this. Is that right, and is it documented anywhere?

like image 809
Chowlett Avatar asked Jun 13 '18 14:06

Chowlett


1 Answers

This is what the (wrongly-maligned) and and or keywords are for. You're supposed to write that as

[1,3].any? &:even? or true

As for why this happens--I can't find documentation for this--but I think it actually has more to do with optional parentheses and the restrictions of unary &.

Unary & is special. "Normal" operators like ~ are essentially syntactic sugar over method calls; you can put them wherever you want. But & is only allowed in method arguments, and even then only at the end.

foo x, &bar
# NameError, determined at runtime because it has to see if any of these names are defined
foo &bar, x
# SyntaxError! Didn't even make it past the parser

y = bar
# NameError
y = &bar
# SyntaxError!

And when you leave parentheses out from a method call, it slurps up pretty much everything, stopping only at super-low-precedence stuff like if/unless/and/or.

foo bar baz if true
# same as
foo(bar(baz)) if true

So your example is equivalent to

[1,3].any?(&:even? || true)

Now if & were somehow high-precendence this is either a totally normal value to be evaluated at runtime true or it's a highly-restricted special syntactic construct &:even?. It's not great to discover syntax errors at runtime, so I guess the devs chose to solve it the easy way: make & super low precedence. That way the parser can just verify the syntax rules and ignore the block argument itself (which has to be evaluated at runtime).

like image 145
Max Avatar answered Nov 18 '22 19:11

Max