Хотя цикл for
и итератор each
часто используются для решения схожих задач, между ними есть принципиальные различия, которые важно понимать для написания идиоматичного Ruby-кода.
# Цикл for - переменная видна снаружи
for item in [1, 2, 3]
last = item
end
puts last #=> 3 (переменная сохраняется)
# Итератор each - переменная ограничена блоком
[1, 2, 3].each { |item| last_each = item }
puts last_each #=> NameError (undefined local variable)
# for возвращает исходную коллекцию
result_for = for i in [1, 2, 3]; i * 2; end
puts result_for #=> [1, 2, 3]
# each возвращает исходную коллекцию
result_each = [1, 2, 3].each { |i| i * 2 }
puts result_each #=> [1, 2, 3]
# each автоматически включает Enumerable методы
[1, 2, 3].each.with_index { |num, i| puts "#{i}: #{num}" }
# for не поддерживает цепочки методов
# for num in [1, 2, 3]; end.with_index - вызовет ошибку
# В большинстве случаев each работает быстрее
require 'benchmark'
array = (1..1_000_000).to_a
Benchmark.bm do |x|
x.report("for:") { for i in array; i * 2; end }
x.report("each:") { array.each { |i| i * 2 } }
end
# Результаты (примерные):
# user system total real
# for: 0.100000 0.000000 0.100000 ( 0.102347)
# each: 0.080000 0.000000 0.080000 ( 0.083211)
each
считается более "рубическим" способом итерации, соответствует принципам ООП:
# Предпочтительный стиль
[1, 2, 3].each do |number|
puts number
end
# Менее идиоматично
for number in [1, 2, 3]
puts number
end
each
можно использовать с разными типами коллекций, поддерживающими итерацию:
# Работает с любым Enumerable
{ a: 1, b: 2 }.each { |k, v| puts "#{k}: #{v}" }
(1..3).each { |i| puts i }
Поведение при изменении коллекции во время итерации:
# for копирует коллекцию перед выполнением
arr = [1, 2, 3]
for i in arr
puts i
arr.delete_if { |x| x == 2 } # безопасно
end
# Выведет 1, 2, 3 (несмотря на удаление)
# each работает с оригинальной коллекцией
arr = [1, 2, 3]
arr.each do |i|
puts i
arr.delete_if { |x| x == 2 } # может привести к неожиданностям
end
# Выведет 1, 3
Резюмируем: хотя for
и each
часто дают одинаковый результат, между ними есть важные различия в области видимости переменных, идиоматичности, производительности и гибкости. В Ruby-сообществе принято отдавать предпочтение итератору each
как более выразительному и безопасному способу итерации, оставляя for
для специфических случаев или совместимости с кодом из других языков.