2013-04-10 1 views
2

Dig это, вот это круто Enumerator (ленивая последовательности) от 1 до (самый большого Float рубин может представлять собой):почему Enumerator включает Enumerable

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 

Входит в лалу. И я имею в виду, что он не возвращается менее чем за 5 секунд.

О вот подсказка:

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

Оказывается, что Enumerator (e) получил свой #drop метод из (модуля) Enumerable смешивает с Enumerator (класса). Теперь почему в мире Ruby пойдет и смешат Enumerable в Enumerator вы спросите? Я не знаю. Но там это, задокументировано как в Enumerator in Ruby 1.9.3, так и в Enumerator in Ruby 2.0.

Проблема, поскольку я вижу, что некоторые методы, определенные в Enumerable, работают или вид работы на Enumerator. Примеры включают #first и #take. По крайней мере один другой: #drop не работает.

Мне кажется, что Enumerator включая Enumerable является ошибкой. Как вы думаете?

PS обратите внимание, что Ruby 2.0 определяет Enumerator::Lazy (подкласс Enumerator), который определяет связку методов 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' не имеет значения. Вы должны либо оставить его, либо заменить его «ленивым», в зависимости от того, что вы хотите точно. –

+0

Спасибо, Марк-Андре. Но в Ruby 1.9.3 нет перечислимого # lazy. Это доступно только в версии 2.0. Я думаю, что одно из моих ключевых недоразумений заключалось в том, что #drop возвратил Enumerator вообще. По какой-то причине, хотя и #drop и #take имеют тривиальные реализации Enumerator, ни один из них не возвращает Enumerator! Даже исправление для [Ruby Bug # 7715 «Lazy enumerators» должно хотеть оставаться ленивым »] (https://bugs.ruby-lang.org/issues/7715) не удалось их исправить. Подумайте об этом, они не могут быть «исправлены», так как это приведет к слому кода, который опирается на возвращающиеся массивы! –

+1

Действительно, 'drop' является« нетерпеливым ». BTW, вы можете играть с 'lazy' в любой версии Ruby с' require 'backports/2.0.0/enumerable/lazy'' –

ответ

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 возвратят Enumerators. Теперь я понимаю, почему это было бы плохо. Много кода зависит от возвращающихся массивов. –

+0

По существу, любой старый (до 1,9) метод Enumerable, который не принимал блок, не мог быть «обновлен», чтобы возвращать Enumerator, так как не было никакого способа, чтобы вызывающий сигнал сигнализировал, что он хочет, чтобы Enumerator вернулся. Переменные # take и #drop не принимают блоки, поэтому их нельзя было бы сделать, чтобы возвращать счетчики. Хотя, конечно, Enumerator :: Lazy # take и #drop возвращают Enumerators, как ожидалось. –

3

Это выбор дизайна, который является общим для многих других структур сбора, а также.

Операции с рубинами не сохраняются в типе. Они всегда возвращают Array, независимо от того, какой тип коллекции они были вызваны. Это то же, что, например, .NET, за исключением того, что тип всегда равен IEnumerable, что является более полезным (потому что больше вещей можно представить как IEnumerable, а не как Array, например бесконечные последовательности) и в то же время менее полезно (потому что интерфейс IEnumerable намного меньше, чем у Array, поэтому на нем меньше операций).

Это позволяет выполнять операции по сборке Ruby после, без дублирования.

Это также означает, что очень легко интегрировать ваши собственные коллекции в рамки коллекции Ruby: просто внесите each, mixin Enumerable и все готово. Если в будущей версии Ruby добавлен новый метод сбора (например, в Ruby 1.9), вам не обязательно делать что-нибудь, оно просто работает с вашей коллекцией.

Еще один выбор дизайна - сделать все операции с типом сохранения. Таким образом, все операции сбора возвращают тип, на который они были вызваны.

Есть несколько языков, которые это делают. Однако он реализуется копией &, вставляющей все методы сбора во все классы коллекции, т. Е. С массивным дублированием кода.

Это означает, что если вы хотите добавить свою собственную коллекцию в фреймворк коллекции, вам необходимо реализовать каждый метод протокола сбора. И если в будущей версии языка добавляются новые методы, вы должны выпустить новую версию своей коллекции.

Структура коллекции Scala 2.8 была в первый раз, когда кто-то выяснил, как выполнять операции по сохранению типов без дублирования кода. Но это было давно после того, как была разработана структура коллекции Ruby. Когда была разработана инфраструктура Ruby, было просто неизвестно, как делать операции по сохранению типов без дублирования кода, а дизайнеры Ruby предпочитают дублирование.

Начиная с Ruby 1.9, на самом деле существует некоторое дублирование. Некоторые методы Hash были продублированы для возврата Hash es вместо Array s. И вы уже упоминали Ruby 2.0's Enumerator::Lazy, который дублирует многие методы Enumerable для возврата Enumerator::Lazy.

Можно использовать те же самые трюки, которые использует Scala в Ruby, но для этого потребовалась бы полная переделка структуры коллекции, что сделало бы всю существующую сборку устаревшей. Scala смог сделать это, потому что в то время практически не было базы пользователей.

+0

Как обычно, хороший ответ. Я бы не сказал, что «Они всегда возвращают« Массив », а также возвращают' Enumerator'. Часть 'Lazy' немного вводит в заблуждение. Методы, которые действуют лениво, являются специализированными, другие - нет. С другой стороны, 'Lazy # to_enum' является специализированным, и этот приятный трюк означает, что любой метод' Enumerable', возвращающий 'Enumerator', будет возвращать' Enumerator :: Lazy' при вызове 'Lazy' без специализации. Подробности в bugs.ruby-lang.org/issues/7715 –