Статьи

Рубин для новичков: итераторы и блоки

Ruby — один из самых популярных языков, используемых в сети. Мы начали новую серию скринкастов здесь, на Nettuts +, которая познакомит вас с Ruby, а также с отличными фреймворками и инструментами, которые сопровождают разработку на Ruby. В этой главе мы поговорим о блоках и итераторах.




На последнем уроке мы говорили о петлях. На самом деле вы не будете использовать циклы в Ruby слишком часто из-за функции, называемой блоками (и — в результате блоков — итераторами). Чтобы освежить вашу память, посмотрите на два следующих вызова метода (вы можете попробовать это в IRB):

1
2
3
4
5
name = «Joe»
 
name.reverse # => «eoJ»
 
name.concat(» the Plumber») # => «Joe the Plumber»

Как вы знаете, круглые скобки после вызова метода обычно необязательны. Мы узнаем сегодня, когда они требуются.

Итак, вот части вызова метода:

  • Объект, получающий метод; name выше.
  • Точка
  • Название метода; reverse или concat выше
  • Аргументы; " the Plumber" во втором примере выше.
  • Блок кода; Будьте на связи!

Первые три части требуются, очевидно. Аргументы и блок кода являются необязательными. Что это за блок кода? Посмотрите на этот пример, и тогда мы обсудим это:

1
2
3
4
5
6
7
sites = [«net», «psd», «mobile»]
 
sites.map!
    site += «.tutsplus.com»
end
 
sites # => [«net.tutsplus.com», «psd.tutsplus.com», «mobile.tutsplus.com»]

В этом случае массив sites является получателем; метод map! , Далее у нас есть блок. Если блок состоит из нескольких строк, вы можете использовать ключевые слова do и end для его разделения. Если вы помещаете его в одну строку, вы можете использовать фигурные скобки (это работает и для многострочных блоков).

После открытия блока у нас есть параметры блока внутри труб ( | ). Что это на самом деле зависит от метода, который вы выполняете. Чаще всего блоки используются в методах итераторов, поэтому параметр блока будет текущим элементом цикла. Если это звучит довольно абстрактно, мы сделаем несколько примеров.


Начнем с рассмотрения итераторов массивов, потому что они чаще всего зацикливаются на чем-либо.

Вместо использования цикла for вы, вероятно, будете использовать each :

1
2
3
4
5
6
7
8
sites = [«net», «psd», «mobile»]
 
sites.each { |site|
    puts «#{site}.tutsplus.com»
}
# net.tutsplus.com
# psd.tutsplus.com
# mobile.tutsplus.com

Это похоже на цикл for; один за другим каждый элемент на sites будет назначен параметру блока site ; тогда код внутри блока будет выполнен.

Если вам интересно, each метод возвращает исходный массив.

Иногда вам захочется вернуть значение из блока. Это не сложно сделать, если вы используете правильный метод.

1
2
3
4
5
# assume sites above
 
sites = sites.map do |s|
    «#{s}.tutsplus.com»
end

Метод map собирает все значения, возвращаемые на каждой итерации блока. Затем массив этих значений возвращается из метода. В этом случае мы переназначаем переменную sites в новый массив.

Хотя есть лучший способ сделать это. Некоторые методы Ruby имеют дубликаты с восклицательным знаком (или ударом); это означает, что они разрушительны: они заменяют ценность, над которой они работают. Таким образом, вышесказанное можно сделать следующим образом:

1
sites.map!

Теперь на sites будет массив значений, возвращаемых из блока.

Однако, не только у массивов есть методы итератора. Числа имеют довольно крутой метод times :

1
2
3
4
5
6
7
8
9
5.times do |i|
    puts «Loop number #{i}»
end
    
# Loop number 0
# Loop number 1
# Loop number 2
# Loop number 3
# Loop number 4

Продолжая кодировать Ruby, вы найдете много полезных методов, которые используют блоки. Теперь давайте посмотрим, как создавать наши собственные блоки.


Теперь, когда вы знакомы с использованием блоков, давайте посмотрим, как писать методы, использующие их преимущества. Вот два других лакомых кусочка, которые вы еще не изучили:

  • Параметры блока не обязательны; Вы можете написать блок, который не использует их.
  • Сами блоки могут быть необязательными. Вы можете написать функцию, которая работает с или без блоков.

Вот что происходит, когда вы вызываете метод, который принимает блок. За кулисами Ruby выполняет некоторый код метода, затем уступая блочному коду. После этого управление возвращается методу. Давайте проверим это.

Поскольку большинство простых итерационных функций встроено в Ruby, мы «переписаем» одну из них. Давайте сделаем each метод на массивах:

01
02
03
04
05
06
07
08
09
10
class Array
    def each2
        i = 0;
        while self[i]
            yield self[i]
            i += 1
        end
        self
    end
end

Как видите, это обычный метод. Помните, что в методе экземпляра ключевое слово self ссылается на экземпляр класса, в данном случае Array . Внутри метода мы используем цикл while для циклического перемещения по элементам в массиве. Затем внутри цикла мы используем ключевое слово yield . Мы передаем это self[i] ; в конечном итоге это будет параметр блока. После этого мы увеличиваем i для цикла и продолжаем.

Если бы мы хотели, чтобы этот метод возвращал массив значений, которые возвращал блок, мы могли бы просто захватить возвращенное значение yield и вернуть, что вместо self мы возвращаем этот массив.

01
02
03
04
05
06
07
08
09
10
11
class Array
    def each2_returning_new_values
        i = 0;
        new_vals = [];
        while self[i]
            new_vals[i] = yield self[i]
            i += 1
        end
        new_vals
    end
end

Давайте закончим разговор о методах. Мы знаем, что использование скобок необязательно … большую часть времени. Вот когда вам нужно использовать круглые скобки при вызове метода.

Иногда у вас будут как параметры метода, так и блок.

1
2
3
obj.some_method «param» { |x|
    #block code here
}

То, что я только что сделал, не сработает; нам нужно использовать круглые скобки в этом случае, потому что в противном случае блок связан с последним параметром. Это только в том случае, если вы используете фигурные скобки для разделения блока; если вы используете doend , скобки не требуются.

В другом случае скобки требуются, когда вы передаете литеральный хеш (не переменную, которая указывает на хеш) в качестве первого параметра метода. Руби будет думать, что это блок из-за фигурной скобки

1
2
arr.push { :name => «Andrew» } # Fails!
arr.push({ :name => «Andrew» }) # Passes