【问题标题】:What's the best way to return an Enumerator::Lazy when your class doesn't define #each?当你的类没有定义 #each 时,返回 Enumerator::Lazy 的最佳方法是什么?
【发布时间】:2013-04-04 09:30:18
【问题描述】:

Enumerable#lazy 依赖于您的可枚举提供 #each 方法。如果你的枚举没有#each 方法,你就不能使用#lazy。现在Kernel#enum_for#to_enum 提供了指定#each 以外的枚举方法的灵活性:

Kernel#enum_for(method = :each, *args)

但是#enum_for 和朋友们总是构造普通的(非惰性的)枚举器,而不是Enumerator::Lazy

我看到 Ruby 1.9.3 中的 Enumerator 提供了这种类似的 #new 形式:

Enumerator#new(obj, method = :each, *args)

不幸的是,构造函数在 Ruby 2.0 中已被完全删除。另外我认为它在Enumerator::Lazy 上根本不可用。所以在我看来,如果我有一个带有方法的类,我想为其返回一个惰性枚举器,如果该类没有 #each,那么我必须定义一些确实定义了 #each 的辅助类。

例如,我有一个 Calendar 类。对我来说,从一开始就列举每个日期是没有意义的。 #each 将毫无用处。相反,我提供了一种从开始日期(懒惰地)枚举的方法:

  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

EachFrom 类看起来像这样:

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

它有效,但感觉很重。也许我应该继承Enumerator::Lazy 并定义一个类似Enumerator 中已弃用的构造函数。你怎么看?

【问题讨论】:

    标签: ruby lazy-evaluation enumerable ruby-2.0


    【解决方案1】:

    我认为你应该使用to_enum返回一个普通的Enumerator

    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
    

    这是大多数红宝石学家所期望的。即使是无限的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 方法,如果您愿意,该方法需要一个参数并包括EnumerableEnumerable 的大多数方法都不起作用,但 each_with_index 和其他几个方法会转发参数,以便立即使用。

    • 没有块的Enumerator.new 消失了,因为应该使用to_enum。请注意,块形式仍然存在。 Lazy 也有一个构造函数,但它是从现有的 Enumerable 开始的。

    • 您声明 to_enum 永远不会创建惰性枚举器,但这并不完全正确。 Enumerator::Lazy#to_enum 专门用于返回惰性枚举器。 Enumerable 上调用 to_enum 的任何用户方法都将使惰性枚举器保持惰性。

    【讨论】:

    • 你让我大吃一惊 Marc-André。我的代码刚刚从白痴变成了惯用语。我不明白 Ruby 希望我们始终使用 Enumerators 而不是 Enumerator::Lazy。每当我们需要一些懒惰的东西时,我们都会向枚举器询问#lazy 版本。缺点可能是我们抽象的用户确实必须了解何时调用#lazy(例如,在调用#drop(n) 之前)。好处是代码清晰。
    • 我在#drop(n) 方面遇到了很多麻烦(并且从中学到了很多)。现在我要在所有地方返回“普通”枚举器,我不得不洒一些 ...lazy.drop(n)... 。所以我定义了一个 drop-like 方法,它简单地推进 Enumerator,让我将它们更改为 ...skip(n)...
    • 对。您绝对可以定义一个像 Enumerable#skip(n) 这样的方法,它会返回一个 Enumerator 而不是像 drop 这样的数组并使用它。
    • 在上面的第三个项目符号中,您说“Enumerator 构造函数没有消失......”我指出非块形式已经消失(允许您指定除 #each 之外的枚举方法的形式)。再次感谢您向我介绍 Marc-André!
    猜你喜欢
    • 1970-01-01
    • 2014-12-21
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多