From: Intransition on


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


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
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
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


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.

First  |  Prev  |  Next  |  Last
Pages: 1 2 3 4 5 6
Prev: what is the meaning of \A and \Z
Next: win32ole question