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?
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 aneach
, not justlazy
.You can define an
each
method that requires a parameter if you like and includeEnumerable
. Most methods ofEnumerable
won't work, buteach_with_index
and a couple of others will forward arguments so these would be usable immediately.The
Enumerator.new
without a block is gone becauseto_enum
is what one should use. Note that the block form remains. There's also a constructor forLazy
, but it's meant to start from an existingEnumerable
.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 onEnumerable
that callsto_enum
will keep a lazy enumerator lazy.
来源:https://stackoverflow.com/questions/15961453/whats-the-best-way-to-return-an-enumeratorlazy-when-your-class-doesnt-define