2019
Enumerator
реализует «внешние итераторы»a = [1, 3, "cat"]
h = {dog: "canine", fox: "vulpine"}
# Создаём нумераторы
enum_a = a.to_enum
enum_h = h.to_enum
enum_a.next # => 1
enum_h.next # => [:dog, "canine"]
enum_a.next # => 3
enum_h.next # => [:fox, "vulpine"]
to_enum
на коллекции или enum_for
с указанием названия итератораa = [1, 3, "cat"]
enum_a = a.each # Созадаём нумератор
enum_a.next # => 1
enum_b = a.to_enum # Создаём нумератор
enum_c = a.enum_for(:each) # Создаём нумератор
Нумераторы являются объектами класса Enumerator
, который включает в себя модуль Enumerable
, что делает доступным для нумераторов всех «классных» методов
loop
Задачей данного метода является бесконечный вызов блока. Если внутри блока используются нумераторы, то выход будет осуществлён, когда закончатся значения в нумераторе
short = [1, 2, 3].to_enum
long = ('a'..'z').to_enum
loop do
puts " #{short.next} - #{long.next}"
end
Метод each_with_index
определён в модуле Enumerable
result = []
['a', 'b', 'c'].each_with_index do |item, index|
result << [item, index]
end
result # => [["a", 0], ["b", 1], ["c", 2]]
Метод with_index
определён в классе Enumerator
result = []
"dog".each_char.with_index do |item, index|
result << [item, index]
end
result # => [["d", 1], ["o", 2], ["g", 2]]
enum_for
Методу enum_for
можно передать название метода-итератора, который будет предоставлять значения последовательности
Если итератор ожидает аргументов, то их следует передать после имени метода
enum_in_threes = (1..7).enum_for(:each_slice, 3)
enum_in_threes.to_a # => [[1, 2, 3], [4, 5, 6],
# [7]]
Нумераторы могут быть созданы на основе обычного блока, который предоставляет очередные значения
numbers = Enumerator.new do |yielder|
number = 0
count = 1
loop do
number += count
count += 1
yielder.yield number
end
end
5.times { print numbers.next, " " }
puts numbers.first(5) # Доступны методы Enumerable
Если генерирующий блок способен предоставлять бесконечное число значений, то его надо указать “ленивым”
numbers = Enumerator.new do |yielder|
number = 0
loop do
number += 1
yielder.yield number
end
end.lazy
puts numbers.all.first(10)
puts numbers.select { |val|
val % 10 == 0 }.first(5)
puts numbers.select { |val|
(val % 3).zero? }.first(10)
Хорошей практикой при создании собственного итератора является возвращение нумератора в случае, когда блок не ассоциирован с данным методом
В результате ваш собственный итератор можно будет использовать как встроенные итераторы: либо в форме ассоциации с болоком, либо в форме получения нумератора
Зачастую необходимо выполнять связные действия, например открытый файл обязательно должне быть закрыт
class File
def self.open_and_process(*args)
f = File.open(*args)
yield f
f.close()
end
end
File.open_and_process("testfile", "r") do |file|
while line = file.gets
puts line
end
end
self
, относятся к классу, а не к объекту класса (“статические”)*args
в аргументах метода open_and_process
собирает все аргументы в массив args
*args
в вызове метода open
раскрывает содержимое массива и записывает их как аргументы метода**opts
для обработки именованных аргументовFile.open
block_given?
проверяет наличие блока и позволяет реализовать альтернативное поведениеДанная техника применяется в итераторах Array, Hash, Enumerable и т.д. для обработки ситуации работы метода с блоком и без него. Если вы не ассоциировали блок с итератором, то он вернёт нумератор
Блоки похожи на анонимные методы, однако с ними можно общаться как с объектами: сохранять в переменные…
class ProcExample
def pass_in_block(&action)
@stored_proc = action
end
def use_proc(parameter)
@stored_proc.call(parameter)
end
end
eg = ProcExample.new
eg.pass_in_block do |param|
puts "The parameter is #{param}"
end
eg.use_proc(99)
Блоки представлены классом Proc
reach = Proc.new do |param|
puts "You called #{param}"
end
reach.call(42) # => You called 42
reach.call('scar') => You called scar
Объекты можно вернуть из методов
def create_block_object(&block)
block
end
reach = create_block_object do |param|
puts "You called #{param}"
end
Блоки можно создавать с помощью метода lambda
Или использовать краткий синтаксис ->
nil
вывод Используйте лямбды, т.к. они предоставляют простую модель использования и гарантии
return
выходит из лямбды, а не из метода, её вызвавшую.return
приведёт к выбросу исключения, если будет вызвано вне связанного методавывод Не используйте ключевое слово return
внутри блоков
Лямбды и блоки определены в едином классе Proc
Для вызова блока он предоставляет следующие методы:
#call(params)
: action.call(42)
#.(params)
: action.(42)
#[params]
: action.[42]
#yield(params)
: action.yield(42)
Все формы равносильны между собой. Рекомендуется использовать #call()
Замыкание - возможность доступа к переменным, объявлённых вне блока
def n_times(thing)
lambda {|n| thing * n }
end
p1 = n_times(23)
p1.call(3) # => 69
p1.call(2) # => 46
thing
метода n_times
находится в области видимости выражений блокаthing
Можно реализовать простой генератор следующим образом
def power_proc_generator
value = 1
lambda { value += value }
end
power_proc = power_proc_generator
puts power_proc.call # => 2
puts power_proc.call # => 4
Современным способом описания коротких лямбд является
lambda
длиннее ->
def my_while(cond, &body)
while cond.call
body.call
end
end
a = 0
my_while -> { a < 3 } do
puts a
a += 1
end
Любой класс, реализующий метод #to_proc
может быть преобразован в блок с помощью оператора &
class Greater
def initialize(greating)
@greating = greating
end
def to_proc
proc {|name| "#{@greating}, #{name}!" }
end
end
hi = Greater.new("Hi")
hey = Greater.new("Hey")
["Bob", "Jane"].map(&hi) #=> ["Hi, Bob!", "Hi, Jane!"]
["Bob", "Jane"].map(&hey) #=> ["Hey, Bob!", "Hey, Jane!"]
Создаёт блок, который будет вызывать метод с именем символа
Создаёт блок, который связывает ключи с их значением
h = {a:1, b:2}
hash_proc = h.to_proc
hash_proc.call(:a) #=> 1
hash_proc.call(:b) #=> 2
hash_proc.call(:c) #=> nil
[:a, :b, :c].map(&h) #=> [1, 2, nil]
Метод Object#method(symbol)
позволяет создать объект класса Method
, который позволяет вызывать данный метод в стиле лямбды
Интерфейс класса Method
повторяет в ключевых моментах интерфейс класса Proc
и де-факто может использоваться как альтернатива блокам и лямбдам
class Thing
def square(n)
n*n
end
end
thing = Thing.new
square_method = thing.method(:square)
square_method.call(9) #=> 81
[ 1, 2, 3 ].map(&square_method) #=> [1, 4, 9]
Классы Proc
и Method
предоставляют методы <<
и >>
, которые позволяют создать цепочки обработки данных
multiplication = lambda {|x| x * x }
addition = lambda {|x| x + x }
(multiplication << addition).call(2) #=> 16
(multiplication >> addition).call(2) #=> 8
Ограничением такого подхода является то, чтобы формат передаваемых данных был в точности тем, что ожидают блоки
Объекты классов Proc
и Method
поддерживают метод curry
, который позволяет создавать блоки с предустановленными аргументами