使用do块与花括号{}的区别

119

如果你是 Ruby 新手,那么请准备好你的新手手套。

下面这两段代码片段是否有任何区别(无论是微不足道还是实用)?

my_array = [:uno, :dos, :tres]
my_array.each { |item| 
    puts item
}

my_array = [:uno, :dos, :tres]
my_array.each do |item| 
    puts item
end

我意识到花括号语法可以让您将代码块放在一行上。

my_array.each { |item| puts item }

但除此之外,是否有任何令人信服的理由来优先使用一种语法而不是另一种?


5
好问题。我也很想知道有经验的 Ruby 开发者更喜欢什么。 - Jonathan Sterling
3
好的,我会尽力进行翻译。下面是需要翻译的内容:问题:如何在Python中检测列表中的重复项?我有一个包含许多元素的Python列表,我想要检测其中是否存在重复项,该怎么做?例如,对于以下列表:[1,2,3,4,5,6,7,8,9,10,1]我希望能够检测到重复的数字1。有没有一种简单的方法可以实现这个功能? - Mike Woodhouse
2
可能是重复的问题 https://dev59.com/enRB5IYBdhLWcg3wuZfo。 - John Topley
1
你也可以写一个 do 块的一行代码,尽管它只在执行类似 eval("my_array.each do |item|;puts item;end") 这样的操作时才有用,但在 irb 或 pry 中不需要 eval 和引号也可以工作。你可能会遇到这种情况更喜欢使用这种方式,但是什么情况下使用最好还需要进一步研究。 - Douglas G. Allen
显示剩余2条评论
5个回答

106

Ruby Cookbook中提到,括号语法的优先级高于do..end语法。考虑以下两个代码片段:

1.upto 3 do |x|
  puts x
end

1.upto 3 { |x| puts x }
# SyntaxError: compile error

只有在使用括号的情况下,第二个示例才能正常工作,1.upto(3) { |x| puts x }



9
好的,明白了。因为优先级顺序不同,当你使用 do 时,你将块作为额外参数传递,但当你使用方括号时,你将块作为方法调用结果的第一个参数传递给左侧。 - Alana Storm
2
我通常喜欢简短的回答,但bkdir的回答更加清晰易懂。 - yakout

76

这是一个有些陈旧的问题,但我想尝试更详细地解释一下{}do..end

就像之前说的一样

括号语法比do..end具有更高的优先级顺序

但是这个有什么区别:

method1 method2 do
  puts "hi"
end

在这种情况下,将使用 do..end 块调用 method1,并将 method2 作为参数传递给 method1!这等同于 method1(method2){ puts "hi" }

但如果你说

method1 method2{
  puts "hi"
}

然后将使用该块调用method2,返回值将作为参数传递给method1。这相当于 method1(method2 do puts "hi" end)

def method1(var)
    puts "inside method1"
    puts "method1 arg = #{var}"
    if block_given?
        puts "Block passed to method1"
        yield "method1 block is running"
    else
        puts "No block passed to method1"
    end
end

def method2
    puts"inside method2"
    if block_given?
        puts "Block passed to method2"
        return yield("method2 block is running")
    else
        puts "no block passed to method2"
        return "method2 returned without block"
    end
end

#### test ####

method1 method2 do 
    |x| puts x
end

method1 method2{ 
    |x| puts x
}

#### 输出 ####

#inside method2
#no block passed to method2
#inside method1
#method1 arg = method2 returned without block
#Block passed to method1
#method1 block is running

#inside method2
#Block passed to method2
#method2 block is running
#inside method1
#method1 arg = 
#No block passed to method1

40

通常,约定俗成的做法是在进行小操作时使用{},例如方法调用或比较等。因此,以下代码是完全合理的:

some_collection.each { |element| puts element }

但是如果您的逻辑稍微复杂,需要跨越多行,则使用 do .. end,例如:

1.upto(10) do |x|
  add_some_num = x + rand(10)
  puts '*' * add_some_num
end

基本上,如果你的块逻辑需要多行并且无法放在同一行上,则使用do .. end,如果您的块逻辑很简单,只是一行简单/单行代码,则使用{}


6
我赞同在多行块中使用 do/end,但如果我在块的末尾链接其他方法,我会选择花括号。从风格上讲,我更喜欢 {...}.method().method() 胜过于 do...end.method().method,但这可能只是我的个人偏好。 - the Tin Man
1
虽然我同意这种写法,但我更喜欢将带有块的方法的结果分配给一个有意义的变量,然后在其上调用另一个方法,例如 result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_method,因为这样更容易理解。但总的来说,我会避免使用“火车失事”式的代码。 - nas
1
我想知道为什么这种风格如此流行。除了“我们一直都是这样做”的原因,还有其他的原因吗? - iGEL

13
有两种常见的风格选择do end{}用于Ruby中的块:
第一种非常常见的风格是由Ruby on Rails推广的,基于单行与多行的简单规则:
- 对于单行块使用大括号{} - 对于多行块使用do end 这是有道理的,因为在一行代码中使用do/end不易读懂,但对于多行块,让一个闭合的}单独出现在一行上,与在ruby中使用end的所有其他内容(如模块、类和方法定义(def等)以及控制结构(ifwhilecase等))不一致。
第二种较少见的风格被称为语义化或"Weirich Braces",由已故杰出的rubyist Jim Weirich提出:
- 对于过程性块使用do end - 对于函数式块使用大括号{} 这意味着当块被评估为其返回值时,它应该是可链接的,而{}大括号对于方法链接更有意义。
另一方面,当块被评估为其副作用时,返回值并不重要,块只是在"做"某些事情,因此不能链接。
这种语法上的区别传达了关于代码块评估的视觉意义,以及你是否应该关心它的返回值。
例如,在这里,代码块的返回值被应用于每个项目:
items.map { |i| i.upcase }

然而,在这里它并没有使用块的返回值。它是按照过程操作,并且对其进行了副作用:

items.each do |item|
  puts item
end

另一个语义化样式的好处是,您不需要更改大括号来执行/结束,只因为添加了一行到块中。
观察发现,偶然地,函数块通常是单行的,而过程块(例如配置)是多行的。因此,遵循Weirich样式最终看起来几乎与Rails样式相同。

1

我多年来一直使用Weirich风格,但最近转而始终使用大括号。我不记得曾经使用过块风格的信息,而且定义有点模糊。例如:

date = Timecop.freeze(1.year.ago) { format_date(Time.now) }
customer = Timecop.freeze(1.year.ago) { create(:customer) }

这些是过程式的还是函数式的?

而且在我看来,行数统计只是无用的。我知道,无论有多少行,为什么要因为添加或删除行而改变样式呢?


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