2013-04-10 151 views
2

挖这一点,这里是一个很酷的Enumerator(懒惰序列)从1到(最大Float红宝石可以代表):为什么枚举包括可枚举

1.9.3-p327 :014 > e = (1..Float::INFINITY).each 

看看我们如何能抓住序列的前:

1.9.3-p327 :015 > e.first 
=> 1 
1.9.3-p327 :016 > e.take(2) 
=> [1, 2] 

这是好东西吧?我也这么认为。但那么这个:

1.9.3-p327 :017 > e.drop(2).first 

进入lala土地。我的意思是,它不会在不到5秒的时间内返回。这里

哦,是一个线索:

1.9.3-p327 :020 > p e.method(:drop) 
#<Method: Enumerator(Enumerable)#drop> 

看来,枚举(e)得到它从Enumerable(模块)#drop方法到Enumerator(类)混合。现在为什么在世界上Ruby会去EnumerableEnumerator你问?我不知道。但在那里,记录在Enumerator in Ruby 1.9.3Enumerator in Ruby 2.0

我看到的问题是Enumerable中定义的一些方法在Enumerator上的工作或种类。示例包括#first#take。至少有一个:#drop不起作用。

在我看来,Enumerator包括Enumerable是一个错误。你怎么看?请注意,Ruby 2.0定义了Enumerator::LazyEnumerator的子类),它定义了一堆非常懒惰的Enumerable方法。有东西在这里闻起来有些腥臭。为什么混入非懒惰的方法和某些情况下破坏的方法(到Enumerator)只能在子类中(Enumerator)转向并提供懒惰的替代方法?

参见:

1.9.3-p327 :018 > p e.method(:first) 
#<Method: Enumerator(Enumerable)#first> 
1.9.3-p327 :020 > p e.method(:drop) 
#<Method: Enumerator(Enumerable)#drop> 
+0

请注意,'e =(1..Float :: INFINITY).each'中的'each'没有区别。你应该放弃它,或者用“懒惰”来取代它,这取决于你想要的东西。 – 2013-04-11 01:14:52

+0

谢谢Marc-André。但是Ruby 1.9.3中没有Enumerable#懒。这仅在2.0版本中可用。我认为我的一个主要误解是假设#drop完全返回了一个Enumerator。出于某种原因,虽然#drop和#take都有可能的微不足道的枚举器实现,但它们都不会返回枚举器!即使修复了[Ruby Bug#7715“懒惰枚举员应该保持懒惰”](https://bugs.ruby-lang.org/issues/7715)也无法解决这些问题。想想看,这些不能“固定”,因为这样做会破坏依赖于返回数组的代码! – 2013-04-11 04:52:55

+1

的确,“drop”是“渴望”的。顺便说一句,你可以在'require'backports/2.0.0/enumerable/lazy''的任何版本的Ruby中使用'lazy'' – 2013-04-11 06:42:39

回答

1

针对第一部分:

“进入拉拉土地,我的意思是它不 小于5秒返回。 “

这行为似乎什么这些方法应该做的是一致的:

take(n) → array # Returns first n elements from enum. 

这意味着你只需要遍历高达N归还。

drop(n) → array # #Drops first n elements from enum, and returns rest elements in an array. 

这意味着它需要剩下的元素才能够返回它们。而且因为你的上限是Float::INFINITY它的行为就像这样。

来源:Enumerable

+0

谢谢fmendez。是的,我完全假设(错误地)#take和#drop会返回枚举器。现在我明白为什么那会很糟糕。很多代码依赖于那些返回的数组。 – 2013-04-11 04:56:18

+0

本质上,任何旧的(前1.9)Enumerable方法,没有一个块,不能被“升级”返回一个枚举器,因为没有办法让调用者指示它想要一个枚举器回来。可枚举#take和#drop不占用块,因此无法返回枚举器。虽然当然Enumerator :: Lazy#take和#drop会按预期返回枚举器。 – 2013-04-11 15:57:29

3

这是一个设计选择是常见的许多其他集合框架以及。

Ruby的收集操作不是类型保留的。他们总是返回Array,无论他们被称为什么类型的集合。这也是,例如,.NET所做的,除非类型总是IEnumerable,这更有用(因为更多的东西可以表示为IEnumerable而不是像Array,例如无限序列)并且同时不太有用(因为IEnumerable的接口比Array的接口小得多,所以你可以对其进行更少的操作)。

这允许Ruby的收集操作被执行一次,没有重复。

这也意味着将自己的集合集成到Ruby的集合框架中非常简单:只需实现each,mixin Enumerable即可完成。如果未来版本的Ruby添加了新的收集方法(例如,Ruby 1.9中的flat_map),则不必执行任何东西,它也适用于您的收藏。

另一个设计选择是使所有集合操作保持类型。所以,所有的集合操作都会返回它们被调用的类型。

有一些语言可以做到这一点。然而,通过复制&将所有收集方法粘贴到所有收集类中来执行,即使用大规模代码复制。

这意味着如果您想将自己的集合添加到集合框架中,则必须实现收集协议的每一种方法。如果该语言的未来版本添加新方法,则必须发布新版本的集合。

Scala 2.8的集合框架是第一次有人想出如何在没有代码重复的情况下进行类型保持集合操作。但是,在Ruby的收集框架设计完成之后,这已经很久了。当设计Ruby的集合框架时,目前还不知道如何在不存在代码重复的情况下进行类型保留集合操作,并且Ruby的设计者选择了重复。

从Ruby 1.9开始,实际上有一些重复。一些Hash方法重复以返回Hash而不是Array s。你已经提到Ruby 2.0的Enumerator::Lazy,它复制了许多Enumerable方法返回Enumerator::Lazy

可能会使用Scala在Ruby中使用的相同技巧,但它需要对集合框架进行完整的返工,这将使每个现有的集合实现都过时。斯卡拉能够做到这一点,因为当时几乎没有任何用户群。

+0

像往常一样,很好的答案。我不会说“他们*总是*返回'数组''也可以返回'枚举'。 “懒惰”部分有点误导。懒惰行事的方法是专业化的,其他方法则不行。另一方面,'Lazy#to_enum'是专用的,这个好的技巧意味着任何返回'Enumerator'的'Enumerable'方法在Lazy上调用时都会返回一个'Enumerator :: Lazy',而没有专门化。详情请参阅bugs.ruby-lang.org/issues/7715 – 2013-04-11 01:37:53