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.
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
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)
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With