2013-04-10 153 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