Hi --

On Wed, 23 Jul 2008, David Flanagan wrote:

> David A. Black wrote:
>> Hi --
>> 
>> I notice that select without a block returns an enumerator in 1.9. I'm
>> wonder if there's any use case for this -- in other words, any reason
>> you would ever do this:
>>
>>   array.select.some_method
>> 
>> Also, it means that you get an enumerator instead of an error message
>> if you do (presumably by accident) this:
>> 
>>>> a = [1,2,3,4,5]
>> => [1, 2, 3, 4, 5]
>>>> puts a.select do |x| x > 3 end
>> #<Enumerable::Enumerator:0x3b7fcc>
>> 
>> I know that something similar will happen with map and others, but if
>> there's no real reason to get an enumerator, I'd prefer that select
>> complained about not having a block.
>> 
>> 
>> David
>> 
>
> David,
>
> I'm sure you've played around with this enough to realize that the enumerator 
> returned by a.select somehow (and I don't really get the magic that is going 
> on) retains its selection semantics.  So:
>
> a = [1,2,3,4,5]
> e = a.select
> e.each { |x| x %2 == 1}  # => [1,3,5]
>
> I know that that is not a convincing use-case.  But begin able to create an 
> enumerator that does selection allows us to chain enumerators:
>
> a.select.with_index { |x,i| x%2 == 1 and i >= 2 }  # => [3,5]
>
> This, I think, is a pretty convincing reason for select to return an 
> Enumerator.

You're right that I knew that, though I hadn't factored it in,
so you're even more right to remind me :-)

But I'm not sure it's enough of a reason. For one thing, I can't seem
to think of any case other than with_index where chaining select to
anything makes sense. I believe that's because with_index is one of
the few methods that Enumerators have that don't come from Enumerable,
so it's designed to be sort of additive, whereas most of the others
just plain "win":

   a.select.map     # might as well be a.map
   a.select.inject  # might as well be a.inject
   etc.

(though inject without a block still blows up.)

Here's a weird one for you (not inconsistent or anything, but I've
found it striking):

>> h = {1=>2, 3=>4, 5=>6}
=> {1=>2, 3=>4, 5=>6}
>> h.select {|k,v| k > 2 }
=> {3=>4, 5=>6}
>> e = h.select
=> #<Enumerable::Enumerator:0x3cae24>
>> e.each {|k,v| k > 2 }              # like your example
=> {3=>4, 5=>6}
>> e.select {|k,v| k > 2 }
=> [[3, 4], [5, 6]]

The e.select returning an array is because e's select is
Enumerable#select, not Hash#select. It's a kind of un-overriding: Hash
overrides select; the enumerator hooks its each to Hash#select; the
enumerator's select reverts to Enumerable#select, sans override.

I can see what's going on but it still looks quite odd. It's a
reminder that enumerators are not proxies but parasites.

Here's another one:

>> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
>> e = a.reject!
=> #<Enumerable::Enumerator:0x395a58>
>> e.each {|b| b > 3 }
=> [1, 2, 3]
>> a
=> [1, 2, 3]

Again, explicable, but kind of weird (to me).

> On the other hand, I suspect there are other iterators for which it is harder 
> to create a convincing use-case.

And the only "penalty" for caring about your block is that someone has
to call to_enum on you. In other words, if select cared about a block,
you could still do this:

   e = a.to_enum(:select)

instead of

   e = a.select

I'm probably being regressive, but (leaving aside the question of how
much of either one would do anyway) I like the first one better.

> I've you've looked at the C code for the 
> iterators, you know that all it takes to make an iterator (implemented in C) 
> return an Enumerator when no block is passed is to insert a single macro 
> call.  My recollection is that these macros were inserted into the core 
> iterators very quickly in the fall of 2007.  The ease with which the change 
> was made makes me wonder if the use case for each iterator was thought 
> through or if it was more a case of ease and consistency: might as well make 
> all iterators return Enumerators because it is easy and most of them do.

The only one I can find, on spot-checking, that doesn't return either
an enumerator or a useful value (like zip) is inject. I don't know why
it still cares about having a block.


David

-- 
Rails training from David A. Black and Ruby Power and Light:
     Intro to Ruby on Rails  July 21-24      Edison, NJ
  *  Advancing With Rails    August 18-21    Edison, NJ
  * Co-taught by D.A. Black and Erik Kastner
See http://www.rubypal.com for details and updates!