如何理解ruby枚举器链中的工作流程

时间:2017-05-09 08:26:00

标签: ruby enumerator

以下代码会产生两种不同的结果。

letters = %w(e d c b a)

letters.group_by.each_with_index { |item, index| index % 3 }
#=> {0=>["e", "b"], 1=>["d", "a"], 2=>["c"]}

letters.each_with_index.group_by { |item, index| index % 3 }
#=> {0=>[["e", 0], ["b", 3]], 1=>[["d", 1], ["a", 4]], 2=>[["c", 2]]}

我认为执行流程是从右到左,数据流是从左到右。该块应作为参数从右向左传递。

使用puts,我发现该块在内部each中执行。

在第一个链中,group_by应该向each询问数据,each将返回index%3的结果,而group_by应该处理结果,屈服于另一个街区。但块是如何通过的?如果该块在each中执行,则each不会传递两个参数itemindex,而只会传递一个参数item

在第二个链中,根据我的理解,each_with_index将首先从each方法接收数据; each收益率为index%3。在这种情况下,each_with_index如何处理index%3

似乎我的理解是错误的。任何人都可以用细节来说明这两个例子,并在这种情况下给出一般的工作流程吗?

1 个答案:

答案 0 :(得分:0)

代理对象

执行和数据流都是从左到右,就像Ruby中的任何方法调用一样。

从概念上讲,它可以帮助从右到左阅读Enumerator个调用链,因为它们是一种proxy object

无阻塞地调用,他们只记得调用哪个方法的顺序。然后,只有在需要时才会真正调用该方法,例如将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个DB查询效率很低。但是,您只能知道在链式方法的最后。这是一个相关的答案(How does Rails ActiveRecord chain “where” clauses without multiple queries?

MyEnumerator

这是一个快速而肮脏的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]]}