From: Benoit Daloze Date: 2010-08-21T23:15:41+09:00 Subject: [ruby-core:31812] Re: [Backport #3715] Enumerator#size and #size= Hi, On 18 August 2010 22:02, Marc-Andre Lafortune wrote: > It would be useful to be able to ask an Enumerator for the number of times it will yield, without having to actually iterate it. [...] > This would print out "Progress: 1 / 20", etc..., while doing the stuff. That seems cool, indeed. > *** Proposed changes *** > > * Enumerator#size * > > call-seq: > �e.size � � � � �-> int, Float::INFINITY or nil > �e.size {block} �-> int > > Returns the size of the enumerator. > The form with no block given will do a lazy evaluation of the size without going through the enumeration. If the size can not be determined then +nil+ is returned. Interesting, though I do not feel INFINITY to be the right answer if it is a loop (but yes, it should obviously be bigger than anything you compare to). > The form with a block will always iterate through the enumerator and return the number of times it yielded. > > �(1..100).to_a.permutation(4).size # => 94109400 > �loop.size # => Float::INFINITY > > �a = [1, 2, 3] > �a.keep_if.size � � � � # => 3 > �a � � � � � � � � � � �# => [1, 2, 3] > �a.keep_if.size{false} �# => 3 > �a � � � � � � � � � � �# => [] > > �[1, 2, 3].drop_while.size � � � � � � # => nil > �[1, 2, 3].drop_while.size{|i| i < 3} �# => 2 What is the interest compared to Enumerable#count ? (also, #size with a block does not feel natural) > a = [1, 2, 3] > a.keep_if.count # => 3 > a # => [] Here the result is identical. > [1, 2, 3].drop_while.count # => 1 Here it is different, and your method behaves strangely. The block is yielded 3 times, but your method returns 2 ? And if you wanted the size of the resulting Array, it seem more obvious to do: [1,2,3].drop_while { |i| i < 3 }.size # => 1 > * Enumerator#size= * Having a mutator on Enumerator does not make sense to me, as previously mentioned by others. -- However, it is really interesting to know the times an enumerator will yield. I believe checking for nil is really not beautiful in the code, so I think it would be nicer if it actually use the block form if it can not be determined directly. But this could introduce unwanted side-effects (the #drop_while is an example). These side-effects should be known, and I do not see any real use for destructive method with #size, do you ? About infinite enumerator, it should just yield forever (so calling #each). I would then propose that Enumerable#count (without block) use this code to give directly the times it would yield, or if it can not, do the real iteration like now. -- And this seems to be already supposed in the documentation of #count: Returns the number of items in enum, where #size is called if it responds to it, otherwise the items are counted through enumeration. So, these enumerators should just provide #size, and it should work. However, it does not behave like that: > a = (1..1000000000000).to_enum # => # > def a.size; 3 end > a.size # => 3 > a.count # => take ages Defining on the class (Range) does not work either. So it seems to be a problem with the implementation (in enum.c, line ~122): it does not check if it #respond_to?(:size): It calls #each with count_all_i() as a block, because there is no block and no argument. count_all_i() being a simple block which just increment a counter (memo). I believe Enumerable#count should respect its documentation, and that your work would help to implement #size on the Enumerator which sizes can be known it immediately. Then we would have a consistent behavior, already described in the documentation ! What do you think ? Regards, B.D.