From: Colin Bartlett Date: 2010-04-10T07:38:33+09:00 Subject: Re: Is it possible, a fully general Enumerable#recursive ? On Fri, Apr 9, 2010 at 8:02 PM, Caleb Clausen wrote: > On 4/9/10, Intransition wrote: >>> Notice: >>>   [1, 2, 3, ['a', 'b', 'c'] ].visit{ |x| x.succ } >>>   => [2, 3, 4, ["b", "c", "d"]] >>> >>> But using Enumerator: >>> >>>   [1, 2, 3, ['a', 'b', 'c'] ].visit.map{ |x| x.succ } >>>   => [2, 3, 4, "b", "c", "d"] >>> >>> Why is it flattening the result? >> To just make that much stranger: >>   [1, 2, 3, ['a', 'b', 'c'] ].visit.with_index{ |x,i| i } >>   => [0, 1, 2, [3, 4, 5]] >> Doesn't flatten, but somehow the index is being carried on to the >> subarray iterations -- that really fracks with my mind. > > This has got to be a bug. It should at least either flatten or not > flatten consistently. I'm not sure that it is a bug. http://www.ruby-doc.org/ruby-1.9/classes/Enumerable.html#M002713 enum.map {| obj | block } => array Returns a new array with the results of running block once for every element in enum. http://www.ruby-doc.org/ruby-1.9/classes/Enumerable.src/M002713.html static VALUE enum_collect(VALUE obj) { VALUE ary; RETURN_ENUMERATOR(obj, 0, 0); ary = rb_ary_new(); rb_block_call(obj, id_each, 0, 0, collect_i, ary); return ary; } The following is Intransition's methods with some added info. It looks to me as though two things are happening. 1. Using #map inside #visit combined with using Enumerable#map seems to (within #visit) produce the structure of the original array, but with nil values instead of the original values. (Changing #map inside #visit to #each gets back the original values.) 2. Irrespective of the internal results from #visit, Enumerable#map collects the successive results of the block for Enumerable#map into a new (flat) array. module Enumerable def visit(&block) puts "#=> Enumerable#visit: self=0x#{self.object_id}= #{self.inspect}" if block_given? then result = map do |e| e.visit(&block) end else result = to_enum(:visit) end puts "#=> Enumerable#visit: result=0x#{result.object_id}= #{result.inspect}" result end end class Object def visit(&block) ; block.call(self) ; end end arr = [ 10, [ 210 ], 30 ] it = arr.visit #=> Enumerable#visit: self=0x13928400= [10, [210], 30] #=> Enumerable#visit: result=0x13927776= # puts ; rr = it.each{ |x| x+7 } #=> Enumerable#visit: self=0x13928400= [10, [210], 30] #=> Enumerable#visit: self=0x13928416= [210] #=> Enumerable#visit: result=0x1562576= [217] #=> Enumerable#visit: result=0x13926848= [17, [217], 37] puts "#=> rr=0x#{rr.object_id}= #{rr.inspect}" #=> rr=0x13926848= [17, [217], 37] puts ; rr = it.map{ |x| x+7 } #=> Enumerable#visit: self=0x13928400= [10, [210], 30] #=> Enumerable#visit: self=0x13928416= [210] #=> Enumerable#visit: result=0x1559360= [nil] #=> Enumerable#visit: result=0x1559840= [nil, [nil], nil] puts "#=> rr=0x#{rr.object_id}= #{rr.inspect}" #=> rr=0x1561088= [17, 217, 37] # Note that the last Enumerable#visit result from it.map # has the same structure as the original nested array # (albeit with nil values - dunno why at the moment), # but that the return value of it.map is a flat array # of the successive results of the block calculation. Changing: if block_given? then result = map do |e| e.visit(&block) end to: if block_given? then result = each do |e| e.visit(&block) end gives for it.map: puts ; rr = it.map{ |x| x+7 } #=> Enumerable#visit: self=0x14484864= [10, [210], 30] #=> Enumerable#visit: self=0x14484896= [210] #=> Enumerable#visit: result=0x14484896= [210] #=> Enumerable#visit: result=0x14484864= [10, [210], 30] puts "#=> rr=0x#{rr.object_id}= #{rr.inspect}" #=> rr=0x1298112= [17, 217, 37]