Hello --

On Sat, 1 Sep 2001, Joel VanderWerf wrote:

> David Alan Black wrote:
>
> > I'm still playing around with this :-)  but one possible source of
> > problems is that you use a boolean test to end the series... which
> > means, for example, that if you change this:
> >
> >> tree = [[0, [1, 2]], 3, [4, 5, [6]]]
> >
> > to this:
> >
> >   tree = [[0, [1, 2]], 3, [4, 5, [6,7,nil]]]
> >
> > it will not process tree[2][2][2].
>
> Hi, David,
>
> Good point. Maybe it should automatically check for respond_to? before
> sending the message. You can do that manually:
>
> tree = [[0, [1, 2]], 3, [4, 5, [6, 7, nil]]]
> for node in tree.by { |x| x.respond_to?(:at) && x.at(2) }
>   p node
> end
>
> To handle this automatically, maybe catching an exception is faster
> than checking for respond_to? How about this change:
>
>     def each
>       cur = @first
>       if @next_name
>         begin
>           loop do
>             break unless cur


That still has the test-for-nil behavior, though.


>             yield cur
>             cur = cur.send @next_name, *@args
>           end
>         rescue NameError
>         end
>       elsif @next_proc
>     ....

What I'm finding is that it's hard to automate because there are
different things that could cause the exception.  For instance, in
your first example (or some approximation thereof :-)

  list = Foo.new(0, Foo.new(1, Foo.new(2, Foo.new(3))))
  list.by(:next_foo).each do |f|
    p f.value
  end

the problem is that the last Foo's next_foo is nil, so the call to
#value, if allowed to happen, raises an exception.  I don't think the
linked list class should have to figure that out, since its job is
just to cycle through all the next_foo's.  And the last Foo does
respond to next_foo.... it just happens to return nil.

Then there's the kind of case where one types 'att' instead of 'at'.


> Although I wouldn't want to do this without checking somehow that the
> exception was generated by the send in this method, rather than something
> called as a result of the send operation.

Yeah, that :-)

Anyway, for what it's worth, here's what I've been working on/with.
It's sketchy, and it still has the test-for-nil problem.  My goal was
just to tighten it up a little.  Also, I have, at least for testing
purposes, eliminated the calling of the first arg when it's a proc.
(So one of your tests, the f = proc... one, doesn't work with this
version.)  I've also taken it out of Enumerable.

   class LinkedListDelegator
     include Enumerable

     def initialize(first, next_spec, *args, &block)
       @cur = first
       @action = if block then block
		 else proc {|t| t.send(next_spec, *args)}
		 end
     end

     def each
       while @cur           # <---- not good
	 yield @cur
	 @cur = @action[@cur]
       end
       self
     end

   end

   class Object
     def by(next_name = nil, *args, &next_getter)
       LinkedListDelegator.new(self, next_name, *args, &next_getter)
     end
   end


David

-- 
David Alan Black
home: dblack / candle.superlink.net
work: blackdav / shu.edu
Web:  http://pirate.shu.edu/~blackdav