Ruby for循环陷阱?

在讨论Ruby循环时,与每个循环相比, Niklas B.最近谈到了循环“不引入新范围”。 我想看一些人们如何看待这个问题的例子。

好吧,我扩展了一个问题:在Ruby的其他地方我们看到了什么apears做/结束块分隔符,但实际上里面没有范围? 还有什么别的……做…结束?

好的,问题的另一个扩展是,有没有办法用花括号{block}编写循环?

让我们通过一个例子来说明这一点:

 results = [] (1..3).each do |i| results << lambda { i } end p results.map(&:call) # => [1,2,3] 

很酷,这是预期的。 现在检查以下内容:

 results = [] for i in 1..3 results << lambda { i } end p results.map(&:call) # => [3,3,3] 

嗯,发生什么事了? 相信我,这些类型的错误是令人讨厌的追踪。 Python或JS开发人员会知道我的意思:)

仅仅这一点是我避免像瘟疫这样的循环的原因,尽管有更多有利于这一立场的好论据。 正如Ben所指出的那样,使用Enumerable的正确方法几乎总是会导致更好的代码,而不是使用简单的旧的,命令式的for循环或者有趣的Enumerable#each 。 例如,上面的例子也可以简洁地写成

 lambdas = 1.upto(3).map { |i| lambda { i } } p lambdas.map(&:call) 

我扩展了一个问题:在Ruby的其他地方我们看到了什么apears做/结束块分隔符,但实际上里面没有范围? 还有什么别的……做…结束?

每个循环结构都可以这样使用:

 while true do #... end until false do # ... end 

另一方面,我们可以在没有do情况下编写其中的每一个(这显然是可取的):

 for i in 1..3 end while true end until false end 

问题的另一个扩展是,有没有办法用花括号{block}来编写循环

不,那里没有。 还要注意术语“块”在Ruby中具有特殊含义。

首先,我将解释为什么你不想使用它,然后解释为什么你可能。

你不想使用的主要原因是它不是惯用的。 如果您使用each ,您可以轻松地将each替换为mapfindeach_with_index而无需对代码进行重大更改。 但是没有for_mapfor_findfor_with_index

另一个原因是,如果你在each块中的块内创建一个变量,并且它之前没有创建过,那么只要该循环存在,它就会保持存在。 一旦你没有使用它们就摆脱变量是一件好事。

现在我会提到你可能想要使用foreach循环each创建一个闭包,如果重复该循环次数过多,那么该循环可能会导致性能问题。 在https://stackoverflow.com/a/10325493/38765中 ,我发布使用while循环而不是块使其变慢。

 RUN_COUNT = 10_000_000 FIRST_STRING = "Woooooha" SECOND_STRING = "Woooooha" def times_double_equal_sign RUN_COUNT.times do |i| FIRST_STRING == SECOND_STRING end end def loop_double_equal_sign i = 0 while i < RUN_COUNT FIRST_STRING == SECOND_STRING i += 1 end end 

times_double_equal_sign持续花费2.4秒,而loop_double_equal_sign则持续0.2至0.3秒。

在https://stackoverflow.com/a/6475413/38765中 ,我发现执行空循环需要1.9秒,而执行空块需要5.7秒。

知道为什么你不想使用,知道你想要使用for ,并且只在需要时使用后者。 除非你对其他语言怀有怀旧情绪。 🙂

好吧,在1.9之前的Ruby中,偶数块也不完美。 他们并不总是引入新的范围:

 i = 0 results = [] (1..3).each do |i| results << lambda { i } end i = 5 p results.map(&:call) # => [5,5,5]