-->

什么是返回一个枚举::懒当你的类没有定义#each的最佳方式?(What's the bes

2019-08-22 03:49发布

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?

Answer 1:

我想你应该回到一个正常的Enumerator使用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

这是大多数Ruby开发者期望的那样。 尽管这是一个无限的Enumerable ,它仍然是可用的,例如:

Calendar.new.each_from(1.year.from_now).first(10) # => [...first ten dates...]

如果他们确实需要一个懒惰的枚举,就可以调用lazy自己:

Calendar.new.each_from(1.year.from_now)
  .lazy
  .map{...}
  .take_while{...}

如果你真的想返回一个懒惰的枚举,你可以叫lazy从你的方法:

  # ...
  def each_from(first)
    return to_enum(:each_from, first).lazy unless block_given?
    #...

我不会推荐它,虽然,因为这将是意想不到的(IMO),可能是矫枉过正,可能会更低高性能。

最后,有一对夫妇在你的问题的误解:

  • 的所有方法Enumerable承担each ,而不只是lazy

  • 您可以定义each需要一个参数,如果你喜欢,包括方法Enumerable 。 大多数方法Enumerable将无法正常工作,但each_with_index和其他几个将转发参数,以便这些会立即可用。

  • Enumerator.new无块了,因为to_enum是一个应该使用什么。 请注意,以块的形式依然存在。 还有一个构造函数Lazy ,但它意味着从现有的启动Enumerable

  • 幽州to_enum从不创建一个懒惰的枚举,但事实并非完全如此。 Enumerator::Lazy#to_enum是专门返回一个懒惰的枚举。 上的任何用户的方法Enumerable调用to_enum将保持慵懒懒枚举。



文章来源: What's the best way to return an Enumerator::Lazy when your class doesn't define #each?