Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make the interface to my enumerator accept feed?

Tags:

ruby

From the docs for Ruby v2.5

e = [1,2,3].map
p e.next           #=> 1
e.feed "a"
p e.next           #=> 2
e.feed "b"
p e.next           #=> 3
e.feed "c"
begin
  e.next
rescue StopIteration
  p $!.result      #=> ["a", "b", "c"]
end

But what about when I create my enum via Enumerator.new?

# a naive rework of the above enum
e2 = Enumerator.new do |y|
  [1,2,3].each do |x|
    y << x
  end
  # raise StopIteration, FED # <= how to get `FED`?
end

p e2.next           #=> 1
e2.feed "a"
p e2.next           #=> 2
e2.feed "b"
p e2.next           #=> 3
e2.feed "c"
begin
  e2.next
rescue StopIteration
  p $!.result      #=> nil
end

How would I modify this to match the API?

Things I have tried:

e2 = Enumerator.new do |y|
  [1,2,3].each do |x|
    @fed = yield
    y << x
  end
  raise StopIteration, @fed
end

e2 = Enumerator.new do |y|
  [1,2,3].each do |x|
    y << yield(x)
  end
  raise StopIteration, y
end

e2 = Enumerator.new do |y|
  enum = [1,2,3].each{|x| yield x }.to_enum
  y << enum.next
  raise StopIteration, y
end

Interestingly they all produce the same error when feed is called a second time:

# Ignoring all the other errors that jump up…
p e2.next           #=> 1
e2.feed "a"
# nil
p e2.next           #=> 2
e2.feed "b"

TypeError: feed value already set

TypeError: feed value already set means it is collecting the the value somewhere, I just don't know how to access it.

The C source for #feed:

static VALUE
enumerator_feed(VALUE obj, VALUE v)
{
    struct enumerator *e = enumerator_ptr(obj);

    if (e->feedvalue != Qundef) {
        rb_raise(rb_eTypeError, "feed value already set");
    }
    e->feedvalue = v;

    return Qnil;
}

So feedvalue is my target. I've dropped into the operation of the method using Pry but can't find a method or variable that appears to relate to feed or feedvalue. Rubinius makes this available explicitly (at least as an instance variable).

I'm stumped.

Any help or insight would be much appreciated.

like image 318
ian Avatar asked Feb 25 '18 17:02

ian


2 Answers

The reason your first example works the way it does is because you've used #map which "passes the array's elements to "yield" and collects the results of "yield" as an array." There's an interesting note about it in Enumerator#feed http://ruby-doc.org/core-2.5.0/Enumerator.html#method-i-feed

Anyway, your custom enumerator will behave the same way as the Array in your first example if you also call map on it:

e2 = Enumerator.new { |y|
  [1,2,3].each do |x|
    y << x
  end
}.map

p e2.next           #=> 1
e2.feed "a"
p e2.next           #=> 2
e2.feed "b"
p e2.next           #=> 3
e2.feed "c"
begin
  e2.next
rescue StopIteration
  p $!.result      #=> ["a", "b", "c"]
end
like image 200
Stephen Crosby Avatar answered Sep 28 '22 20:09

Stephen Crosby


Your first example is enumerator with a yield method of :map:

e = [1,2,3].map
=> #<Enumerator: [1, 2, 3]:map>

Your second example is an enumerator with yield method of :each.

e2 = Enumerator.new do |y|
  [1,2,3].each do |x|
    y << x
  end
  # raise StopIteration, FED # <= how to get `FED`?
end
=> #<Enumerator: #<Enumerator::Generator:0x007fa69b056b50>:each> 

You should use to_enum or enum_for with the yield method of your choice:

[1,2,3].to_enum(:map)
=> #<Enumerator: [1, 2, 3]:map>

Using ::new in the manner below is now deprecated, so I would NOT recommend using it in favor of to_enum or enum_for which offer the same functionality:

Enumerator.new([1,2,3], :map)

Summary

To sum this up, #map is the method being called by your first iterator, and when it is called, the return values of it will decide the value of the result. When using #each like your other examples, it doesn't matter what you end your block (#feed) with since it won't affect the return value of #each.

like image 24
Cody Gustafson Avatar answered Sep 28 '22 21:09

Cody Gustafson