Tuesday, May 3, 2011

Блоки и итераторы в Ruby

В Ruby блоки кода могут использоваться для того, чтобы создавать функции обратного вызова, а также итераторы.
Блоки кода - это то, что находится между скобками { } или do... end

Это блок:
{puts "Hi, man"}
И это тоже блок:
do
  food.buy("bread")
  bread.put
end
Итак, есть два варианта оформить блок. В чем разница?
Вообще, скобки { } более тесно связаны между собой, чем пара do/end.
Можно придерживаться стиля, когда скобки используют, чтобы оформить блок в одну строку, а пару do/end - чтобы оформить блок, который занимает больше одной строки.

Важное свойство блока состоит в том, что его можно ассоциировать с методом.
Например, так
greet { puts "Hi" }
В примере метод greet ассоциирован с блоком, включающим в себя вывод "Hi".
Если метод с параметрами, то блок ставится в конце
verbose_greet("Dave", "loyal customer") { puts "Hi" }
Метод может вызывать ассоциированный блок несколько раз, используя Ruby оператор yield.
Посмотрим пример:
def call_block
  puts "Start of method"
  yield
  yield
  puts "End of method"
end
call_block { puts "In the block" }
Этот код выведет:
Start of method
In the block
In the block
End of method
Теперь стало понятно, что происходит. Сначала мы вызвали метод call_block, а потом оператор yield вызывает ассоциированный блок.
Можно передавать параметры оператору yield.
def call_block
  yield("hello", 99)
end
call_block {|str, num| ... }
Блоки кода используются, чтобы создавать итераторы: методы, которые возвращают элементы какой-нибудь коллекции, например, массива.
Создадим массив:
animals = %w( ant bee cat dog elk )
Ниже представлен блок, созданный с целью, получить итератор:
animals.each {|animal| puts animal }
Выведет:
ant
bee
cat
dog
elk
Но где же тут оператор yield? Давайте посмотрим на метод each, который находится в классе Array. Это псевдокод, который отражает суть.
def each
  for each element
    yield(element)
  end
end
Также, как и в прошлом примере, оператор yield вызывает ассоциированный с методом each блок.

Вот так можно использовать метод each:
[ 'cat', 'dog', 'horse' ].each {|name| print name, " " }
('a'..'e').each {|char| print char }
В Ruby есть и другие циклические конструкции, которые используют тот же подход.
5.times { print "*" }
3.upto(6) {|i| print i }
10.downto(5) {|i| print i }
0.step(50,5) {|i| print i }


* This source code was highlighted with Source Code Highlighter.

Статья написана по материалам книги "Programming Ruby" Dave Thomas.