当你的类没有定义 #each 时,返回 Enumerator::Lazy 的最佳方法是啥?
Posted
技术标签:
【中文标题】当你的类没有定义 #each 时,返回 Enumerator::Lazy 的最佳方法是啥?【英文标题】: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
中已弃用的构造函数。你怎么看?
【问题讨论】:
【参考方案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
方法,如果您愿意,该方法需要一个参数并包括Enumerable
。 Enumerable
的大多数方法都不起作用,但 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é!以上是关于当你的类没有定义 #each 时,返回 Enumerator::Lazy 的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
iOS开发:UINavigationController自定义返回按钮,系统导航支持侧滑返回
对于不实现 Iterable 的类,如何使用 for-each 循环