为什么在Ruby中`puts(nil or 4)`会失败?

19

当我执行:

puts(nil or 4)

Ruby抱怨:

SyntaxError: syntax error, unexpected keyword_or, expecting ')'
为什么呢?puts(nil || 4)这样可以运行,但我想知道为什么or不能。我原本认为它们的唯一区别在于运算符优先级。
(我知道表达式nil or 4看起来似乎没用,因为它总是返回4。这只是为了简单起见举的例子。我的实际表达式是Integer(ENV['WD'] or 4)。)

5
有趣的是,puts (nil or 4) 可以正常运行 - 在 put 后加了一个空格。 - Wand Maker
3
似乎出了个bug。 - Ilya
1
如果这是一个错误,那么“and”也有相同的错误。 - Dave Schweisguth
puts((nil or 4))也能工作?! - Dave Schweisguth
@DaveSchweisguth:请看我的回答 :) - Sergio Tulentsev
3个回答

15

简短回答

因为这是Ruby语法规定的。

较长回答

andor关键字被设计用于控制流构造中。考虑以下示例:

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

在 Ruby 中,or 可以很好地与可选括号配合使用,这是由于 Ruby 的解析规则(伪BNF)

简而言之,参数列表(CALL_ARGS)是由逗号分隔的 ARG 列表。现在,大多数任何东西都是 ARG(例如,通过是 PRIMARY 的类定义),但不是未装饰的 EXPR。如果您用括号括起来一个表达式,那么它将匹配“复合语句”的规则,因此将成为 PRIMARY,这是一个 ARG。这意味着

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`

您可以阅读上面提到的语法,以了解其工作原理。

BONUS:类定义作为有效ARG的示例

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

# >> bar # this is the "value" of the class definition
# >> second argument

3
非常好的解释! - Andrey Deineko
2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Dave Schweisguth
2
@DaveSchweisguth:是的,完全正确。我从来不使用它们。 :) - Sergio Tulentsev
2
@DaveSchweisguth 而且该风格指南建议避免使用它们。https://github.com/bbatsov/ruby-style-guide#no-and-or-or - SteveTurczyn
1
关于@SteveTurczyn提供的样式指南:为了那些浏览器不兼容github JavaScript锚点的用户(我使用的是“旧”Opera浏览器):您可以在该长页面中搜索句子“The and and or keywords are banned”以到达相关部分。 - Niccolo M.
显示剩余6条评论

11

这是因为orand比方法调用的优先级更低。你的表达式被解释为:

{puts(nil} or {4)}

{}代表分组。语法错误来自于表达式。

puts(nil

(而且以下内容也会引发语法错误):

4)
如果你通过在表达式周围加上一对括号来强制分组,则它将按照你预期的方式工作:

如果你通过在表达式周围加上一对括号来强制分组,则它将按照你预期的方式工作:

puts((nil or 4))

请注意,最外层的括号用于方法调用而不是分组,因此仅有一对括号并不会改变分组。

另一种方法是,如果你在一对括号中间添加一个空格以明确表示它是用于分组的,则也可以起到相同的作用:

puts (nil or 4)

7

@Sergio Tulentsev(和@sawa)提供了一个很好的答案,但我想重新表述一下,以便将来我能快速理解:

Ruby允许我们在函数调用中省略括号。也就是说,不需要写成:

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

我们可以做到以下几点:
func1 ARG, ARG, ARG or func2 ARG, ARG, ARG

然而,为了使最后一行像第一行那样运行,“or”不能成为ARG的顶层操作符(否则最后一行将被解释为func1(ARG, ARG, ARG or func2 ARG, ARG, ARG))。实际上,当我们查看BNF时,我们发现ARG没有直接提到“or”/“and”(这意味着在此处使用是非法的)。
但是ARG仍然可以使用“or”:通过将表达式放在括号中。在BNF中,我们看到这是ARG可以分支到的PRIMARY可选项(因为PRIMARY又会分支到'(' COMPSTMT ')')。
现在,关于为什么func (1 or 2)func((1 or 2))有效,而func(1 or 2)无效:
- func(1 or 2)是BNF中称为FUNCTION的部分,它扩展为OPERATION ['(' [CALL_ARGS] ')'],这意味着ARG是“1 or 2”,但正如我们所见,ARG不能包含“or”,因此是无效的。 - func((1 or 2))也是OPERATION ['(' [CALL_ARGS] ')'],但这里ARG是“(1 or 2)”,这是一个有效的ARG(参见上面提到的PRIMARY)。 - func (1 or 2)是BNF中称为COMMAND的部分,它扩展为OPERATION CALL_ARGS,这意味着ARG是“(1 or 2)”,这是一个有效的ARG(参见上面提到的PRIMARY)。

其他答案的很好的总结,并且还有额外的推理。 - Wand Maker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接