Enumerable#lazy
relies on your enumerable providing an #each
method. If your enumerable doesn't have an #each
method you can't use #lazy
. Now Kernel#enum_for
and #to_enum
provide the flexibility to specify an enumeration method other than #each
:
Kernel#enum_for(method = :each, *args)
But #enum_for
and friends always construct plain (non-lazy) enumerators, never Enumerator::Lazy
.
I see that Enumerator
in Ruby 1.9.3 offers this similar form of #new:
Enumerator#new(obj, method = :each, *args)
Unfortunately that constructor has been completely removed in Ruby 2.0. Also I don't think it was ever available at all on Enumerator::Lazy
. So it seems to me that if I have a class with a method I want to return a lazy enumerator for, if that class has no #each
then I have to define some helper class that does define #each
.
For instance, I've got a Calendar
class. It doesn't really make sense for me to offer to enumerate every date from the beginning of all time. An #each
would be useless. Instead I offer a method that enumerates (lazily) from a starting date:
class Calendar
...
def each_from(first)
if block_given?
loop do
yield first if include?(first)
first += step
end
else
EachFrom.new(self, first).lazy
end
end
end
And that EachFrom
class looks like this:
class EachFrom
include Enumerable
def initialize(cal, first)
@cal = cal
@first = first
end
def each
@cal.each_from(@first) do |yielder, *vals|
yield yielder, *vals
end
end
end
It works but it feels heavy. Maybe I should subclass Enumerator::Lazy
and define a constructor like that deprecated one from Enumerator
. What do you think?
Enumerator::Lazy. Enumerator::Lazy is a special type of Enumerator , that allows constructing chains of operations without evaluating them immediately, and evaluating values on as-needed basis.
Enumerator, specifically, is a class in Ruby that allows both types of iterations – external and internal. Internal iteration refers to the form of iteration which is controlled by the class in question, while external iteration means that the environment or the client controls the way iteration is performed.
I think you should return a normal Enumerator
using to_enum
:
class Calendar
# ...
def each_from(first)
return to_enum(:each_from, first) unless block_given?
loop do
yield first if include?(first)
first += step
end
end
end
This is what most rubyists would expect. Even though it's an infinite Enumerable
, it is still usable, for example:
Calendar.new.each_from(1.year.from_now).first(10) # => [...first ten dates...]
If they actually need a lazy enumerator, they can call lazy
themselves:
Calendar.new.each_from(1.year.from_now)
.lazy
.map{...}
.take_while{...}
If you really want to return a lazy enumerator, you can call lazy
from you method:
# ...
def each_from(first)
return to_enum(:each_from, first).lazy unless block_given?
#...
I would not recommend it though, since it would be unexpected (IMO), could be an overkill and will be less performant.
Finally, there are a couple of misconceptions in your question:
All methods of Enumerable
assume an each
, not just lazy
.
You can define an each
method that requires a parameter if you like and include Enumerable
. Most methods of Enumerable
won't work, but each_with_index
and a couple of others will forward arguments so these would be usable immediately.
The Enumerator.new
without a block is gone because to_enum
is what one should use. Note that the block form remains. There's also a constructor for Lazy
, but it's meant to start from an existing Enumerable
.
You state that to_enum
never creates a lazy enumerator, but that's not entirely true. Enumerator::Lazy#to_enum
is specialized to return a lazy enumerator. Any user method on Enumerable
that calls to_enum
will keep a lazy enumerator lazy.
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