16

在讨论 Ruby 循环时,Niklas B.最近谈到了与每个循环相比的 for 循环“不引入新范围”。我想看看一些例子,说明人们对此有何感受。

好的,我扩展这个问题:在 Ruby 中,我们还能在哪里看到 apears 的作用/结束块分隔符,但实际上里面没有范围?除了 for ... do ... end 还有什么?

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

4

3 回答 3

14

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

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 正确指出的那样,使用正确的方法 fromEnumerable几乎总是比使用普通的旧命令式for循环或更高级的Enumerable#each. 例如,上面的例子也可以简洁地写成

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

我扩展了这个问题:在 Ruby 中,我们还能在哪里看到 apears 的作用/结束块分隔符,但实际上里面没有范围?除了 for ... do ... end 还有什么?

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

while true do
  #...
end

until false do
  # ...
end

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

for i in 1..3
end

while true
end

until false
end

问题的另一个扩展,有没有一种方法可以用花括号 { block } 编写 for 循环

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

于 2012-05-01T11:29:17.827 回答
3

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

您不想使用的主要原因for是它不习惯。如果您使用each,您可以轻松地将其替换each为 amap或 afind或a ,each_with_index而无需对代码进行重大更改。但是没有for_mapor for_findor for_with_index

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

现在我会提到为什么你可能想要使用for. 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,知道为什么要使用for,并且只在需要时使用后者。除非你对其他语言感到怀旧。:)

于 2012-05-01T23:33:16.690 回答
1

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

i = 0
results = []
(1..3).each do |i|
  results << lambda { i }
end
i = 5
p results.map(&:call)  # => [5,5,5]
于 2012-05-01T13:56:21.230 回答