Руби закрутить ловушку?
В обсуждении циклов 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]