Ruby Forum Ruby > Enumerable#to_a can run forever

Posted by Benjamin Kudria (bkudria)
on 25.12.2007 00:47
It's possible to implement an each method in a class mixing in
Enumerable that runs forever, and thus causing, for example, to_a to run
forever.  One such class is mathn's Prime class:

require 'mathn'
Prime.new.to_a # <- runs forever, never returns

This seems a bit broken to me, and I was writing a program where I
needed the first n primes, or all the primes under 200, for example, so
I re-wrote Enumerable#to_a like so to make my code more idiomatic:

----8<----
module Enumerable
  def to_a(n = nil)
    result = []
    inclusion_condition = true
    return result if n.to_i <= 0 and !block_given?
    each do |*elements|
      elements = elements.first if elements.size == 1
      if block_given?
        values = result.last
        inclusion_condition = yield(*values) unless values.nil?
      else
        inclusion_condition = n != result.size
      end

      if inclusion_condition
        result << elements
      else
        result.pop if block_given?
        break result
      end
    end
    result
  end
end
----8<----

This allows, for example, the following:
Prime.new.to_a(10) # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] , the first
10 primes
Prime.new.to_a {|prime| prime < 20} # => [2, 3, 5, 7, 11, 13, 17, 19] ,
all the primes under 20

Passed an integer paramater, the code will return an array that long, or
until the Enumerable runs out.  Passed a block, it will return an array
of elements, adding elements until the block evaluates to false.

This code surely could use some hacking, refactoring, and possibly bug
fixing.  I've made a post[1] on http://refactormycode.com just for that
purpose.

Thoughts?

[1]
http://refactormycode.com/codes/196-enumerable-to_a-with-terminating-conditions

-Ben Kudria
--
http://ben.kudria.net | Jabber: ben@kudria.net
Posted by Yukihiro Matsumoto (Guest)
on 25.12.2007 03:19
(Received via mailing list)
Hi,

In message "Re: Enumerable#to_a can run forever"
    on Tue, 25 Dec 2007 08:47:48 +0900, Benjamin Kudria <ben@kudria.net> 
writes:

|require 'mathn'
|Prime.new.to_a # <- runs forever, never returns
|
|This seems a bit broken to me, and I was writing a program where I
|needed the first n primes, or all the primes under 200, for example, so
|I re-wrote Enumerable#to_a like so to make my code more idiomatic:

|This allows, for example, the following:
|Prime.new.to_a(10) # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] , the first
|10 primes
|Prime.new.to_a {|prime| prime < 20} # => [2, 3, 5, 7, 11, 13, 17, 19] ,
|all the primes under 20

Hmm, in 1.9, you have Enumerable#take to work like your to_i(num), and
Enumerable#take_while like your to_i{...}.

  p Prime.new.take(10) # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
  p Prime.new.take_while{|prime| prime < 20} # => [2, 3, 5, 7, 11, 13, 
17, 19]

              matz.
Posted by Benjamin Kudria (bkudria)
on 25.12.2007 03:27
Yukihiro Matsumoto wrote:
> Hmm, in 1.9, you have Enumerable#take to work like your to_i(num), and
> Enumerable#take_while like your to_i{...}.
> 
>   p Prime.new.take(10) # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
>   p Prime.new.take_while{|prime| prime < 20} # => [2, 3, 5, 7, 11, 13, 
> 17, 19]
> 
>               matz.

Aaah, ok.  Those would be excellent.  I look forward to 1.9!  Thank you!
However, what about Prime.new.to_a ?  That would still run forever, and 
the unsuspecting coder might be caught by surprise.
Posted by Yukihiro Matsumoto (Guest)
on 25.12.2007 04:39
(Received via mailing list)
Hi,

In message "Re: Enumerable#to_a can run forever"
    on Tue, 25 Dec 2007 11:27:29 +0900, Benjamin Kudria <ben@kudria.net> 
writes:

|Aaah, ok.  Those would be excellent.  I look forward to 1.9!  Thank you!
|However, what about Prime.new.to_a ?  That would still run forever, and 
|the unsuspecting coder might be caught by surprise.

It's no way to avoid entering infinite loop when you do some
operations on infinite enumerable, such as Prime.  For example, trying
#sort or #max on Prime, or even putting it as a splat will cause
infinite loop.  Adding an argument or a block to #to_a does not
improve the situation a lot, I think.

              matz.
Posted by Benjamin Kudria (bkudria)
on 25.12.2007 05:00
Yukihiro Matsumoto wrote:
> It's no way to avoid entering infinite loop when you do some
> operations on infinite enumerable, such as Prime.  For example, trying
> #sort or #max on Prime, or even putting it as a splat will cause
> infinite loop.  Adding an argument or a block to #to_a does not
> improve the situation a lot, I think.
> 
>               matz.

Fair enough - but then does it make sense to have these operations 
defined?
Enumerable seems to mean the same thing as "countable", but it includes 
methods that apply to only countably finite sets.  Perhaps there should 
be a way to distinguish between sets that are countable finite and those 
that are countably infinite?  Does 1.9 include provisions for handling 
infinite lists or sets?

Thanks,
Ben Kudria
--
http://ben.kudria.net | Jabber: ben@kudria.net
Posted by Yukihiro Matsumoto (Guest)
on 25.12.2007 05:10
(Received via mailing list)
Hi,

In message "Re: Enumerable#to_a can run forever"
    on Tue, 25 Dec 2007 13:00:48 +0900, Benjamin Kudria <ben@kudria.net> 
writes:

|Fair enough - but then does it make sense to have these operations 
|defined?

Because it's very pragmatically useful for most of the case.
Theoretical cleanliness is not my concern.

|Perhaps there should 
|be a way to distinguish between sets that are countable finite and those 
|that are countably infinite?

And adding one burden to library implementer (including me)?

|Does 1.9 include provisions for handling 
|infinite lists or sets?

I think you mean generator.  If so, it has been supported since 1.8.

              matz.
Posted by Benjamin Kudria (bkudria)
on 26.12.2007 01:33
Yukihiro Matsumoto wrote:
> |Fair enough - but then does it make sense to have these operations 
> |defined?
> 
> Because it's very pragmatically useful for most of the case.
> Theoretical cleanliness is not my concern.

Aah, I see.  I guess I agree.

> |Perhaps there should 
> |be a way to distinguish between sets that are countable finite and those 
> |that are countably infinite?
> 
> And adding one burden to library implementer (including me)?

Fair enough :).  It would probably be overly confusing for the coder, 
too.

> |Does 1.9 include provisions for handling 
> |infinite lists or sets?
> 
> I think you mean generator.  If so, it has been supported since 1.8.

You are correct, I see Generators in 1.8 can do everything I want. 
Seems I don't know Ruby as well as I thought. :)

Thanks for clarifying everything.

Ben Kudria
--
http://ben.kudria.net | Jabber: ben@kudria.net