I ran in to two problematic issues in the course of this. The first is
that String is Enumerable in 1.8. This leads to infinite loops (since
iterating over a string return another string), so an exception had to
be made for String. (I can't think of any other class this would ever
be an issue for, and in Ruby 1.9 String is no longer Enumerable, so I
think accounting for String as an exception is enough, but please
correct me if I'm wrong!). The second issue, has to do with
Enumerator, which simply does not behave in manner one would expect --
it's not even consistent across different methods. Basically I've
decided to ignore this with the exception of using #map instead of
#each for the internal loops. I'm hoping the Enumerator itself will be
improved down the road so the issue will naturally resolve itself.
I cant' say I am 100% happy with #walk, but #visit I find very
elegant. In any case, without further ado here's #walk
module Enumerable
# Recursive iteration over enumerables of the same type.
#
# [1, 2, [:a, :b]].walk{ |e| p e }
#
# produces
#
# 1
# 2
# :a
# :b
#
# Without a block, #recurse returns an Enumerator upon
# which other Enumerable methods can be invoked.
#
# [:a, :b, [:c, :d]].walk.map{ |e| e.succ }
# # => [:b, :c, [:d, :e]]
#
# [:a, :b, [:c, :d]].walk.map.with_index{ |e,i| i }
# # => [0, 1, [2, 3]]
#
# This method differs from #visit in that it only walks
# like classes, not any Enumerable. For example,
# used on an Array it will only walk over arrays.
#
# To walk other +types+, supply the types as arguments.
#
# [1, 2, 3..4].walk(Range).map{ |x| x.succ }
# #=> [1, 2, [3, 4], [:A, :B]]
#
# TODO: Currently #walk uses #map for the internal loop
# instead of #each b/c Enumerator does not fully work
# as expected. Eventually his may be fixed.
#
# TODO: Classes that inherit Enumerable should probably
# override this method to check for possible conversion
# (eg. to_ary for Array), if applicable, rather than only
# checking the class type. We could generalize this by
# supporting "kown-thy-self" library in the future.
def walk(*types, &block)
types = [self.class] if types.empty?
if block
map do |e|
case e
when String
block.call(e)
when *types
e.walk(&block)
when Enumerable # do this ?
e
else
block.call(e)
end
end
else
to_enum(:walk, *types)
end
end
end
And here's #visit
module Enumerable
# Recursively iterate over the Enumerable's elements.
# If an element is also Enumerable, then it will also
# be visited, and so on. This work in a unique way:
# by defining #visit in a class one can control how
# visiting behaves for that class.
#
# [1,2,3,[:a,:b,:c]].visit{ |x| x.succ }
# #=> [2,3,4,[:b,:c,:d]]
#
# NOTE: This uses #map internally, so one can utilize
# a mapping of the results. #each would have been
# preferred as it is more efficient. In which case the
# Enumerator syntax would need to be used to get a map,
# e.g.
#
# [1,2,3,[:a,:b,:c]].visit.map{ |x| x.succ }
#
# Unfortunately this does not work as expected --it
# flattens the result. For this reason #map is used
# as the underlying iterator instead of #each.
def visit(&block)
if block_given?
map do |e|
e.visit(&block)
end
else
to_enum(:visit)
end
end
end
class Object
def visit(&block)
block.call(self)
end
end
class String
def visit(&block)
block.call(self)
end
end
You'll notice the two implementations are quite different, though they
do mostly the same thing. I've debated using the same technique for
both but the implementations presented seem more conducive to their
respective differences.
_______________________________________________
facets-universal mailing list
facets-u...@rubyforge.org
http://rubyforge.org/mailman/listinfo/facets-universal
On Apr 9, 7:00 pm, Trans <transf...@gmail.com> wrote:
> Working on general purpose recursive iterator for Enumerable has
> proven tricker than I expected. At this point I have narrowed things
> down to two methods, #walk and #visit. They differ by how restrictive
> they are in determining what they traverse. #walk iterates over like
> classes, i.e. walking an array only recurses over sub-arrays. #visit
> traverses anything it can.
[snip code]
So much for that code. After pulling my hair out for far too long,
I've decided to implement my own Recursor class, akin to Enumerator,
to handle all recursive functions.