Ruby是否和"{"、"}"一样读取"do"和"end"?

5
Ruby有两种不同的语法来编写块。这里有:
do |something|
    ...
end

同时还有

{ |something|
    ...
}

我意识到这个代码只是用括号替换了关键词“do”和“end”。这让我想知道,当Ruby使用这两种语法时,它们之间是否有重大差别。或者说,Ruby是以同样的方式处理它们,可以互换使用吗?如果它们完全可互换,那么为什么要同时包含两个呢?


1
@falsetru,这不是重复的。这个问题问的是do...end{...}是否可以互换使用,以及何时可以互换。你链接的那个问题则关注风格,它询问在可互换时应该使用哪一个。 - Boris Stitnicky
2个回答

9
不,它并不是这样的。作为Ruby的一般规则,如果两个东西看起来相似,那么它们之间肯定存在微妙的差别,使得它们各自独特且必要。 {}并不总是扮演块分隔符的角色。当它们不扮演块分隔符的角色时(例如构建哈希时,{ a: 1, b: 2 }),它们不能被do ... end替代。但是当花括号确实用于分隔块时,它们几乎总是可以被do ... end替代。但要小心,因为有时候这可能会改变你语句的含义。这是因为{ ... }的优先级更高,它们比do ... end绑定更紧密。
puts [ 1, 2, 3 ].map { |e| e + 1 }         # { ... } bind with #map method
2
3
4
#=> nil

puts [ 1, 2, 3 ].map do |e| e + 1 end      # do ... end bind with #puts method
#<Enumerator:0x0000010a06d140>
#=> nil

就相反的情况而言,do ... end 在作为块分隔符的角色中,并不总是可以被花括号替代。
[ 1, 2, 3 ].each_with_object [] do |e, obj| obj << e.to_s end  # is possible
[ 1, 2, 3 ].each_with_object [] { |e, obj| obj << e.to_s }   # invalid syntax

在这种情况下,您需要将有序参数放在括号中:
[ 1, 2, 3 ].each_with_object( [] ) { |e, obj| obj << e.to_s }  # valid with ( )

这些句法规则的结果是你可以写成这样:
[ 1, 2, 3 ].each_with_object [ nil ].map { 42 } do |e, o| o << e end
#=> [ 42, 1, 2, 3 ]

而且上述还展示了一种情况,即{}不能被do/end替代的情况:
[ 1, 2, 3 ].each_with_object [ nil ].map do 42 end do |e, o| o << e end
#=> SyntaxError

Lambda语法中的->稍有不同,它可以同时接受以下两种形式:
foo = -> x do x + 42 end  # valid
foo = -> x { x + 42 }     # also valid

此外,doend本身并不总是限定一个块。特别是,这个
for x in [ 1, 2, 3 ] do
  puts x
end

这个
x = 3
while x > 0 do
  x -= 1
end

这个
x = 3
until x == 0 do
  x -= 1
end

表面上包含了do ... end关键字,但它们之间没有真正的代码块。它们内部的代码并不引入新的作用域,只是用来重复几个语句的语法。在这里不能使用花括号,并且可以省略do而编写语法,例如:
for x in [ 1, 2, 3 ]
  puts x
end

# same for while and until

无论是 {...} 还是 do...end 作为定界符都可以互换使用的情况,选择使用哪种取决于编程风格,并且 在另一个问题中广泛讨论(我在这里的一个例子中引用了那里被接受的答案)。在这种情况下,我个人更倾向于强调可读性而不是严格的规则。

哇!非常感谢您提供如此周到和深思熟虑的答案!我需要一些时间来消化这些内容。可能会有一些后续问题需要请教您。 - Michael Nail
一个 Ruby 程序员需要花费一定的时间来理解这些内容。 - Boris Stitnicky
begin enddo end 有什么区别? - Til

0
一般来说,{ }块语法用于单行代码块,而do ... end语法用于多行代码块。两者之间的一个区别是,在花括号块中,参数必须放在括号内。例如: some_method(param1, param2) { ... }
some_method param1, param2 do
   ...
end

"123".gsub /./ do |s| (s.to_i + 1).to_s end => "234",但是"123".gsub /./ { |s| (s.to_i + 1).to_s }会引发语法错误。另一方面,[1,2,3].each_with_object [] { |e,a| a << e + 1 } => [2, 3, 4]。知道为什么吗? - Cary Swoveland
@CarySwoveland 不好意思,我也不知道,在块规范中也找不到。 - August
因为 Ruby 解析器需要一些提示来判断 { 是哈希的开始还是块的开始。Ruby 解析器试图将 some_method p1, p2, {...} 解析为哈希参数,而不是块;另一方面,它将 some_method {...} 视为块参数。 - user1375096
感谢您的解释,@huocp。那很有道理。 - Cary Swoveland

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