Using an Enumerator
in Ruby is pretty straightforward:
a = [1, 2, 3]
enumerator = a.map
enumerator.each(&:succ) # => [2, 3, 4]
But can I do something similar with nested collections?
a = [[1, 2, 3], [4, 5, 6]]
a.map(&:map) # => [#<Enumerator: [1, 2, 3]:map>, #<Enumerator: [4, 5, 6]:map>]
But now how do I get [[2, 3, 4], [5, 6, 7]]
?
This could always be done with a block:
a = [[1, 2, 3], [4, 5, 6]]
a.map { |array| array.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]
But I was wondering if there was a way that avoided the use of a block, partly because I find it annoying to have to type |array| array
and also partly because I'm curious to find a way to do it.
Ideally, it would feel like this psuedocode:
a.map.map(&:succ)
# perhaps also something like this
a.map(&:map).apply(&:succ)
The only way I know of doing this is to do the following:
a = [[1, 2, 3], [4, 5, 6]]
a.map { |b| b.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]
Mainly because of the combination of Array#map
/Enumerable#map
and Symbol#to_proc
, you cannot pass a second variable to the block that #map
yields for, and thus pass another variable to the inner #map
:
a.map(1) { |b, c| c } # c => 1, but this doesn't work :(
So you have to use the block syntax; Symbol#to_proc
actually returns a proc that takes any number of arguments (you can test this by doing :succ.to_proc.arity
, which returns -1
). The first argument is used as the receiver, and the next few arguments are used as arguments to the method - this is demonstrated in [1, 2, 3].inject(&:+)
. However,
:map.to_proc.call([[1, 2, 3], [4, 5, 6]], &:size) #=> [3, 3]
How? :map.to_proc
creates this:
:map.to_proc # => proc { |receiver, *args, &block| receiver.send(:map, *args, &block) }
This is then called with the array of arrays as an argument, with this block:
:size.to_proc # => proc { |receiver, *args, &block| receiver.send(:size, *args, &block) }
This results in .map { |receiver| receiver.size }
being effectively called.
This all leads to this - since #map
doesn't take extra arguments, and passes them to the block as parameters, you have to use a block.