简体   繁体   中英

Ruby for loop a trap?

In a discussion of Ruby loops, Niklas B. recently talked about for loop 'not introducing a new scope', as compared to each loop. I'd like to see some examples of how does one feel this.

OK, I expand the question: Where else in Ruby do we see what apears do/end block delimiters, but there is actually no scope inside? Anything else apart from for ... do ... end?

OK, One more expansion of the question, is there a way to write for loop with curly braces { block } ?

Let's illustrate the point by an example:

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

Cool, this is what was expected. Now check the following:

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

Huh, what's going on? Believe me, these kinds of bugs are nasty to track down. Python or JS developers will know what I mean :)

That alone is a reason for me to avoid these loops like the plague, although there are more good arguments in favor of this position. As Ben pointed out correctly, using the proper method from Enumerable almost always leads to better code than using plain old, imperative for loops or the fancier Enumerable#each . For instance, the above example could also be concisely written as

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

I expand the question: Where else in Ruby do we see what apears do/end block delimiters, but there is actually no scope inside? Anything else apart from for ... do ... end?

Every single one of the looping constructs can be used that way:

while true do
  #...
end

until false do
  # ...
end

On the other hand, we can write every one of these without the do (which is obviously preferrable):

for i in 1..3
end

while true
end

until false
end

One more expansion of the question, is there a way to write for loop with curly braces { block }

No, there is not. Also note that the term "block" has a special meaning in Ruby.

First, I'll explain why you wouldn't want to use for , and then explain why you might.

The main reason you wouldn't want to use for is that it's un-idiomatic. If you use each , you can easily replace that each with a map or a find or an each_with_index without a major change of your code. But there's no for_map or for_find or for_with_index .

Another reason is that if you create a variable within a block within each , and it hasn't been created before-hand, it'll only stay in existance for as long as that loop exists. Getting rid of variables once you have no use for them is a good thing.

Now I'll mention why you might want to use for . each creates a closure for each loop, and if you repeat that loop too many times, that loop can cause performance problems. In https://stackoverflow.com/a/10325493/38765 , I posted that using a while loop rather than a block made it slower.

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 consistently took 2.4 seconds, while loop_double_equal_sign was consistently 0.2 to 0.3 seconds faster.

In https://stackoverflow.com/a/6475413/38765 , I found that executing an empty loop took 1.9 seconds, whereas executing an empty block took 5.7 seconds.

Know why you wouldn't want to use for , know why you would want to use for , and only use the latter when you need to. Unless you feel nostalgic for other languages. :)

Well, even blocks are not perfect in Ruby prior to 1.9. They don't always introduce new scope:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM