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