From: Intransition on 9 Apr 2010 14:49 On Apr 9, 11:01 am, Intransition <transf...(a)gmail.com> wrote: > On Apr 7, 5:49 pm, Intransition <transf...(a)gmail.com> wrote: > > > For the last couple of days I've been trying to write an Enumerable > > method called #recursive. Rather than create a bunch of methods like > > #recursive_each, #recursive_map, #recursive_sort, etc. I figured that > > it should be possible to create a single #recursive method that > > returned an Enumerator, or barring that a Functor, that would handle > > any enumerable method, e.g. recursive.each, recursive.map, > > recursive.sort, and so on. But I have yet to figure out fully general > > solution. > > Working on this more I currently have the method #visit (see the code > below). It almost works, but it has this one issue that makes no sense > to me, and I wonder what is going on under the hood in Enumerator for > it do this. It has the air of a bug to me --or at least a feature > deficiency. > > 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.
From: Intransition on 9 Apr 2010 14:53 On Apr 9, 2:03 pm, Robert Dober <robert.do...(a)gmail.com> wrote: > On Thu, Apr 8, 2010 at 5:51 AM, David Masover <ni...(a)slaphack.com> wrote: > > On Wednesday 07 April 2010 04:49:15 pm Intransition wrote: > <snip> > > >> This works for #each and #map but not #sort. > > > #sort isn't a method of Enumerable, it's a method of Array. > > ruby-1.9.1-p378 > Enumerable.instance_methods.grep /sort/ > => [:sort, :sort_by] > However you will need to define #<=> on the return value of recursive. Yep. That's trick b/c comparing and array or other enumerable to a non enumerable raises an error. So sorting with this is probably out of the question. On a side note, I am not so sure that raising an error is best. Why not just assume that too non-comparable things are equal?
From: Caleb Clausen on 9 Apr 2010 15:02 On 4/9/10, Intransition <transfire(a)gmail.com> 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. > On a side note, I am not so sure that raising an error > is best. Why not just assume that too non-comparable things are equal? Maybe it would help you write a #visit that sorts properly, but in general, I would expect that if I try to sort a collection containing non-comparable objects it's because I made a mistake and put the wrong thing into the collection somehow. So I want to see that error.
From: Colin Bartlett on 9 Apr 2010 18:38 On Fri, Apr 9, 2010 at 8:02 PM, Caleb Clausen <vikkous(a)gmail.com> wrote: > On 4/9/10, Intransition <transfire(a)gmail.com> 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= #<Enumerator:0x1a90ac0> 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]
From: Intransition on 10 Apr 2010 14:05
On Apr 9, 6:38 pm, Colin Bartlett <colin...(a)googlemail.com> wrote: > 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= #<Enumerator:0x1a90ac0> > > 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] Thanks, that helped me understand a little better. After working on this for far too long, I have concluded that a highly generalized implementation of recursion for all (or at least most) enumerable methods not feasible. But there are still many things in common between recursion methods, so instead I have created a Recursor class to act something like an Enumerator for recursive operations. Thanks for the help. |