您可能会发现分解这些表达式并使用 IRB 或 PRY 来查看 Ruby 正在做什么很有用。让我们开始吧:
[1,2,3].each_with_index.map { |i,j| i*j }
让
enum1 = [1,2,3].each_with_index
#=> #<Enumerator: [1, 2, 3]:each_with_index>
我们可以使用Enumerable#to_a(或Enumerable#entries)将enum1 转换为一个数组,以查看它将传递给下一个枚举器(如果有的话,则传递给一个块):
enum1.to_a
#=> [[1, 0], [2, 1], [3, 2]]
这并不奇怪。但是enum1 没有块。相反,我们将方法发送给它Enumerable#map:
enum2 = enum1.map
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:each_with_index>:map>
您可能会将其视为一种“复合”枚举器。这个枚举器确实有一个块,因此将其转换为数组将确认它将与enum1 相同的元素传递到块中:
enum2.to_a
#=> [[1, 0], [2, 1], [3, 2]]
我们看到数组[1,0] 是enum2 传入块的第一个元素。 “消歧”应用于此数组以分配块变量值:
i => 1
j => 0
也就是说,Ruby 正在设置:
i,j = [1,0]
我们现在可以通过向 each 发送带有块的方法来调用 enum2:
enum2.each { |i,j| i*j }
#=> [0, 2, 6]
接下来考虑:
[1,2,3].map.each_with_index { |i,j| i*j }
我们有:
enum3 = [1,2,3].map
#=> #<Enumerator: [1, 2, 3]:map>
enum3.to_a
#=> [1, 2, 3]
enum4 = enum3.each_with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:map>:each_with_index>
enum4.to_a
#=> [[1, 0], [2, 1], [3, 2]]
enum4.each { |i,j| i*j }
#=> [0, 2, 6]
由于 enum2 和 enum4 将相同的元素传递到块中,我们看到这只是做同一件事的两种方式。
这是第三个等效链:
[1,2,3].map.with_index { |i,j| i*j }
我们有:
enum3 = [1,2,3].map
#=> #<Enumerator: [1, 2, 3]:map>
enum3.to_a
#=> [1, 2, 3]
enum5 = enum3.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:map>:with_index>
enum5.to_a
#=> [[1, 0], [2, 1], [3, 2]]
enum5.each { |i,j| i*j }
#=> [0, 2, 6]
为了更进一步,假设我们有:
[1,2,3].select.with_index.with_object({}) { |(i,j),h| ... }
我们有:
enum6 = [1,2,3].select
#=> #<Enumerator: [1, 2, 3]:select>
enum6.to_a
#=> [1, 2, 3]
enum7 = enum6.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:select>:with_index>
enum7.to_a
#=> [[1, 0], [2, 1], [3, 2]]
enum8 = enum7.with_object({})
#=> #<Enumerator: #<Enumerator: #<Enumerator: [1, 2, 3]:
# select>:with_index>:with_object({})>
enum8.to_a
#=> [[[1, 0], {}], [[2, 1], {}], [[3, 2], {}]]
enum8 传入块的第一个元素是数组:
(i,j),h = [[1, 0], {}]
然后应用消歧来为块变量赋值:
i => 1
j => 0
h => {}
请注意,enum8 显示在enum8.to_a 的三个元素中的每一个都传递了一个空哈希,但这当然只是因为 Ruby 不知道传入第一个元素后哈希会是什么样子。