Руби закрутить ловушку?

В обсуждении циклов Ruby Niklas B. недавно говорилось о том, что цикл "не вводит новую область" по сравнению с каждым циклом. Я хотел бы посмотреть несколько примеров того, как это чувствуется.

O.K., я разберу вопрос: где еще в Ruby мы видим, что происходит с идентификаторами /end block delimiters, но на самом деле нет области внутри? Что-нибудь еще, кроме того, чтобы... сделать... конец?

O.K., Еще одно расширение вопроса, есть ли способ написать для цикла с фигурными фигурными скобками {block}?

Ответы

Ответ 1

Обозначим точку на примере:

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 будут знать, что я имею в виду:)

Это только повод для меня избегать таких петель, как чума, хотя есть более веские аргументы в пользу этой позиции. Как Бен указал правильно, использование надлежащего метода из Enumerable почти всегда приводит к лучшему коду, чем использование простых старых, императивных циклов for или fancier Enumerable#each, Например, приведенный выше пример также можно кратко описать как

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

Я развожу вопрос: где еще в Ruby мы видим, что происходит с идентификаторами /end block delimiters, но на самом деле нет области внутри? Что-нибудь еще, кроме того, чтобы... сделать... конец?

Каждая из этих циклов может быть использована следующим образом:

while true do
  #...
end

until false do
  # ...
end

С другой стороны, мы можем написать каждый из них без do (который, очевидно, предпочтительнее):

for i in 1..3
end

while true
end

until false
end

Еще одно расширение вопроса, есть ли способ написать для цикла с фигурными фигурными скобками {block}

Нет, нет. Также обратите внимание, что термин "блок" имеет особое значение в Ruby.

Ответ 2

Во-первых, я объясню, почему вы не захотите использовать for, а затем объясните, почему вы можете.

Основная причина, по которой вы бы не хотели использовать for, - это то, что она не идиоматична. Если вы используете each, вы можете легко заменить это each на map или find или each_with_index без существенного изменения вашего кода. Но там нет for_map или for_find или for_with_index.

Другая причина заключается в том, что если вы создадите переменную внутри блока внутри each, и она не была создана заранее, она останется только в наличии до тех пор, пока существует этот цикл. Избавление от переменных, как только вы им не пользуетесь, - это хорошо.

Теперь я расскажу, почему вы можете использовать for. each создает замыкание для каждого цикла, и если вы повторяете этот цикл слишком много раз, этот цикл может вызвать проблемы с производительностью. В fooobar.com/questions/305304/... я сообщил, что использование цикла 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 секунды быстрее.

В fooobar.com/questions/281824/... я обнаружил, что выполнение пустого цикла заняло 1,9 секунды, тогда как выполнение пустого блока заняло 5,7 секунды.

Знайте, почему вы не захотите использовать for, знаете, почему вы хотите использовать for, и используйте только последний, когда вам нужно. Если вы не чувствуете ностальгию по другим языкам.:)

Ответ 3

Ну, даже блоки не идеальны в Ruby до 1.9. Они не всегда вводят новый масштаб:

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