Making fibers enumerable

Hi,

Most of the `Enumerable` methods can be used for fiber based generators if
`Fiber` is made enumerable.

class Fiber
  include Enumerable
  def each
    loop { yield self.resume }
  end
end

def fib_gen
  Fiber.new {
    a, b = 0, 1
    while true
      Fiber.yield a
      a, b = b, a + b
    end
  }
end

# Find the fibonacci number greater than 1000
fib_gen.find {|x| x > 1000 }
# take first 10 fibonacci numbers
fib_gen.take 10
# take_while numbers are smaller than 1000
fib_gen.take_while {|x| x < 1000 }

If it could be the default behavior, it would be very useful. Some methods
won't be applicable, particularly for non-terminating generators in their
default incarnation viz. drop_while. It can either be re-written to advance
the generator using Fiber#resume or it can be left upto the user to handle
infinite sequences correctly.

If I understand correctly, writing generators was one of the purposes of
fibers. Why can't fibers be made enumerable by default?

I am not sure how that interacts with the original intent of Fibers:
their main purpose is to bring coroutines to Ruby.

How would your example look if there was another generation that you
wanted to do concurrently? Because for the single threaded generator
case there is already a tool: Enumerator.new. The example in the
documentation even uses Fibonacci Numbers as example. :slight_smile:

http://www.ruby-doc.org/core/classes/Enumerator.html#M000299

fib_gen = Enumerator.new { |y|
  a = b = 1
  loop {
    y << a
    a, b = b, a + b
  }
}

Now you can do exactly the same as you did with your example

# Find the fibonacci number greater than 1000
fib_gen.find {|x| x > 1000 }
# take first 10 fibonacci numbers
fib_gen.take 10
# take_while numbers are smaller than 1000
fib_gen.take_while {|x| x < 1000 }

For this Fiber would be the wrong tool.

Kind regards

robert

···

On Wed, May 18, 2011 at 9:39 AM, Rahul Kumar <rahulsinner@gmail.com> wrote:

Hi,

Most of the `Enumerable` methods can be used for fiber based generators if
`Fiber` is made enumerable.

class Fiber
include Enumerable
def each
loop { yield self.resume }
end
end

def fib_gen
Fiber.new {
a, b = 0, 1
while true
Fiber.yield a
a, b = b, a + b
end
}
end

# Find the fibonacci number greater than 1000
fib_gen.find {|x| x > 1000 }
# take first 10 fibonacci numbers
fib_gen.take 10
# take_while numbers are smaller than 1000
fib_gen.take_while {|x| x < 1000 }

If it could be the default behavior, it would be very useful. Some methods
won't be applicable, particularly for non-terminating generators in their
default incarnation viz. drop_while. It can either be re-written to advance
the generator using Fiber#resume or it can be left upto the user to handle
infinite sequences correctly.

If I understand correctly, writing generators was one of the purposes of
fibers. Why can't fibers be made enumerable by default?

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

The Enumerator.new uses Fibers internally. Check enumerator.c in the sources;
besides that, how would you achieve the needed effect without coroutines?

···

On Wed, 18 May 2011 20:12:43 +0900, Robert Klemme wrote:

How would your example look if there was another generation that you
wanted to do concurrently? Because for the single threaded generator
case there is already a tool: Enumerator.new. The example in the
documentation even uses Fibonacci Numbers as example. :slight_smile:

class Enumerator - RDoc Documentation

fib_gen = Enumerator.new { |y|
  a = b = 1
  loop {
    y << a
    a, b = b, a + b
  }
}

Now you can do exactly the same as you did with your example

# Find the fibonacci number greater than 1000
fib_gen.find {|x| x > 1000 }
# take first 10 fibonacci numbers
fib_gen.take 10
# take_while numbers are smaller than 1000
fib_gen.take_while {|x| x < 1000 }

For this Fiber would be the wrong tool.

--
   WBR, Peter Zotov.

> If I understand correctly, writing generators was one of the purposes of
> fibers. Why can't fibers be made enumerable by default?

I am not sure how that interacts with the original intent of Fibers:
their main purpose is to bring coroutines to Ruby.

http://en.wikipedia.org/wiki/Coroutines

Right. But aren't semi co-routines(don't send data; just receive) used to
implement generators?

class Enumerator - RDoc Documentation

fib_gen = Enumerator.new { |y|
a = b = 1
loop {
    y << a
   a, b = b, a + b
}
}

Oh, didn't know about this - somehow "The Ruby Programming Language" doesn't
mention it or I missed it. I simply translated my Python code to Ruby.

Thanks for the info Robert.

···

On Wed, May 18, 2011 at 4:42 PM, Robert Klemme <shortcutter@googlemail.com>wrote:

How would your example look if there was another generation that you
wanted to do concurrently? Because for the single threaded generator
case there is already a tool: Enumerator.new. The example in the
documentation even uses Fibonacci Numbers as example. :slight_smile:

class Enumerator - RDoc Documentation

fib_gen = Enumerator.new { |y|
a = b = 1
loop {
y << a
a, b = b, a + b
}
}

Now you can do exactly the same as you did with your example

# Find the fibonacci number greater than 1000
fib_gen.find {|x| x > 1000 }
# take first 10 fibonacci numbers
fib_gen.take 10
# take_while numbers are smaller than 1000
fib_gen.take_while {|x| x < 1000 }

For this Fiber would be the wrong tool.

The Enumerator.new uses Fibers internally. Check enumerator.c in the
sources;

That's an implementation detail of Enumerator. The point is that the
primary purpose of Fiber is to be able to build concurrency without
preemption but with manual control over when one task yields to
another task. Enumerator.new on the other hand is a tool for
generation of sequences of items which is precisely what the Fibonacci
example is all about. There is no concurrency.

besides that, how would you achieve the needed effect without coroutines?

class X
  include Enumerable

  Callback = Struct.new :code do
    def <<(x)
      code
      self
    end
  end

  def initialize(&code)
    @code = code
  end

  def each(&b)
    cb = Callback.new b
    @code[cb]
    self
  end
end

fib_gen = X.new { |y|
a = b = 1
loop {
   y << a
   a, b = b, a + b
}
}

# Find the fibonacci number greater than 1000
p fib_gen.find {|x| x > 1000 }
# take first 10 fibonacci numbers
p fib_gen.take 10
# take_while numbers are smaller than 1000
p fib_gen.take_while {|x| x < 1000 }

Kind regards

robert

···

On Wed, May 18, 2011 at 2:14 PM, Peter Zotov <whitequark@whitequark.org> wrote:

On Wed, 18 May 2011 20:12:43 +0900, Robert Klemme wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Ruby fibers can be either semi-coroutines or full co-routines. And
while replacing continuations as the basis for implementing generators
was certainly an important motivation for implementing fibers in Ruby,
that's not the only thing fibers can do, and enumerability may not
make sense for other fibers. So, usually, Enumerator::Generator is
used for generators (though that uses Fiber behind the scenes) and
Fiber is used directly for other use cases.

···

On Wed, May 18, 2011 at 8:50 AM, Rahul Kumar <rahulsinner@gmail.com> wrote:

On Wed, May 18, 2011 at 4:42 PM, Robert Klemme > <shortcutter@googlemail.com>wrote:

> If I understand correctly, writing generators was one of the purposes of
> fibers. Why can't fibers be made enumerable by default?

I am not sure how that interacts with the original intent of Fibers:
their main purpose is to bring coroutines to Ruby.

Coroutine - Wikipedia

Right. But aren't semi co-routines(don't send data; just receive) used to
implement generators?