Объясните разницу между циклами 'for' и итераторами 'each'.ruby-31

Хотя цикл for и итератор each часто используются для решения схожих задач, между ними есть принципиальные различия, которые важно понимать для написания идиоматичного Ruby-кода.

Основные различия

1. Область видимости переменных

# Цикл 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)

2. Возвращаемое значение

# 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]

3. Поддержка методов Enumerable

# each автоматически включает Enumerable методы
[1, 2, 3].each.with_index { |num, i| puts "#{i}: #{num}" }

# for не поддерживает цепочки методов
# for num in [1, 2, 3]; end.with_index - вызовет ошибку

4. Производительность

# В большинстве случаев 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)

Семантические различия

1. Идиоматичность

each считается более "рубическим" способом итерации, соответствует принципам ООП:

# Предпочтительный стиль
[1, 2, 3].each do |number|
  puts number
end

# Менее идиоматично
for number in [1, 2, 3]
  puts number
end

2. Гибкость

each можно использовать с разными типами коллекций, поддерживающими итерацию:

# Работает с любым Enumerable
{ a: 1, b: 2 }.each { |k, v| puts "#{k}: #{v}" }
(1..3).each { |i| puts i }

3. Обработка исключений

Поведение при изменении коллекции во время итерации:

# 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

Когда что использовать?

Используйте each когда:

  • Нужен идиоматичный Ruby-код
  • Требуется цепочка методов (with_index, with_object)
  • Важна область видимости переменных
  • Работаете с разными типами коллекций

Используйте for когда:

  • Нужна совместимость с шаблонами из других языков
  • Требуется явная читаемость в математических вычислениях
  • Работаете с простыми диапазонами (1..10)

Резюмируем: хотя for и each часто дают одинаковый результат, между ними есть важные различия в области видимости переменных, идиоматичности, производительности и гибкости. В Ruby-сообществе принято отдавать предпочтение итератору each как более выразительному и безопасному способу итерации, оставляя for для специфических случаев или совместимости с кодом из других языков.