2017-05-09 33 views
3

下面的代码产生两个不同的结果。如何理解红宝石枚举器链中的工作流程

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

看来我的理解有点不对。任何人都可以在这些情况下说明这两个例子的细节并给出一般的工作流程吗?

回答

0

代理对象

两者执行和数据流从左至右,与红宝石任何方法调用。

从概念上讲,它可以帮助从右向左阅读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:[email protected]_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:[email protected]_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:[email protected]_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:[email protected]_enumerator.rb:50> to #<Enumerator:0x0000000266b938> 
# {0=>[["e", 0], ["b", 3]], 1=>[["d", 1], ["a", 4]], 2=>[["c", 2]]} 
+0

我非常感谢您的解释和示例代码[e,d,c,b,a]产生一个链接到方法group_by的枚举器。在那一步中,枚举器不会被执行。在第二个应用中,将each_index_with应用于第一个结果(枚举数),each_index_with产生这样一个结果:'{0 => [“e”,“b”] ....}'。没有任何障碍通过,group_by如何分组?从枚举数到'{0 => [“e”,“b”] ...}“的过程是神秘的! – pyb1993