What's the best way to return an Enumerator::Lazy when your class doesn't define #each?
enumerable, lazy-evaluation, ruby, ruby-2.0
Solution
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.
Problem
`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?