Прокси-объекты
И выполнение, и потоки данных идут слева направо, как и при вызове любого метода в Ruby.
Концептуально это может помочь читать цепочки вызовов Enumerator
справа налево, поскольку они представляют собой своего рода прокси-объект.
Вызванные без блока, они просто запоминают, в каком порядке был вызван какой метод. Затем метод действительно вызывается только тогда, когда это необходимо, например, когда Enumerator
преобразуется обратно в Array
или элементы печатаются на экране.
Если такой метод не вызывается в конце цепочки, в основном ничего не происходит:
[1,2,3].each_with_index.each_with_index.each_with_index.each_with_index
# #<Enumerator: ...>
[1,2,3].each_with_index.each_with_index.each_with_index.each_with_index.to_a
# [[[[[1, 0], 0], 0], 0], [[[[2, 1], 1], 1], 1], [[[[3, 2], 2], 2], 2]]
Такое поведение позволяет работать с очень большими потоками объектов без необходимости передавать огромные массивы между вызовами методов. Если вывод не нужен, ничего не вычисляется. Если в конце необходимо 3 элемента, вычисляются только 3 элемента.
Шаблон прокси широко используется в Rails, например, с ActiveRecord::Relation
:
@person = Person.where(name: "Jason").where(age: 26)
В этом случае было бы неэффективно запускать 2 запроса к БД. Однако вы можете узнать это только в конце цепных методов. Вот соответствующий ответ (Как Rails ActiveRecord связывает предложения «где» без нескольких запросов?)
Мой Перечислитель
Вот быстрый и грязный класс MyEnumerator
. Это может помочь вам понять логику вызовов методов в вашем вопросе:
class MyEnumerator < Array
def initialize(*p)
@methods = []
@blocks = []
super
end
def group_by(&b)
save_method_and_block(__method__, &b)
self
end
def each_with_index(&b)
save_method_and_block(__method__, &b)
self
end
def to_s
"MyEnumerable object #{inspect} with methods : #{@methods} and #{@blocks}"
end
def apply
result = to_a
puts "Starting with #{result}"
@methods.zip(@blocks).each do |m, b|
if b
puts "Apply method #{m} with block #{b} to #{result}"
else
puts "Apply method #{m} without block to #{result}"
end
result = result.send(m, &b)
end
result
end
private
def save_method_and_block(method, &b)
@methods << method
@blocks << b
end
end
letters = %w[e d c b a]
puts MyEnumerator.new(letters).group_by.each_with_index { |_, i| i % 3 }.to_s
# MyEnumerable object ["e", "d", "c", "b", "a"] with methods : [:group_by, :each_with_index] and [nil, #<Proc:0x00000001da2518@my_enumerator.rb:35>]
puts MyEnumerator.new(letters).group_by.each_with_index { |_, i| i % 3 }.apply
# Starting with ["e", "d", "c", "b", "a"]
# Apply method group_by without block to ["e", "d", "c", "b", "a"]
# Apply method each_with_index with block #<Proc:0x00000000e2cb38@my_enumerator.rb:42> to #<Enumerator:0x00000000e2c610>
# {0=>["e", "b"], 1=>["d", "a"], 2=>["c"]}
puts MyEnumerator.new(letters).each_with_index.group_by { |_item, index| index % 3 }.to_s
# MyEnumerable object ["e", "d", "c", "b", "a"] with methods : [:each_with_index, :group_by] and [nil, #<Proc:0x0000000266c220@my_enumerator.rb:48>]
puts MyEnumerator.new(letters).each_with_index.group_by { |_item, index| index % 3 }.apply
# Apply method each_with_index without block to ["e", "d", "c", "b", "a"]
# Apply method group_by with block #<Proc:0x0000000266bd70@my_enumerator.rb:50> to #<Enumerator:0x0000000266b938>
# {0=>[["e", 0], ["b", 3]], 1=>[["d", 1], ["a", 4]], 2=>[["c", 2]]}
person
Eric Duminil
schedule
09.05.2017