2013-04-12 2 views
5

Enumerable#lazy полагается на ваш счетчик, предоставляя метод #each. Если ваш счетчик не имеет метода #each, вы не можете использовать #lazy. Теперь Kernel#enum_for и #to_enum обеспечивают гибкость, чтобы указать способ перечисления, кроме #each:Какой лучший способ вернуть Enumerator :: Lazy, когда ваш класс не определяет #each?

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

Но #enum_for и друзья всегда строить простые (без ленивых) счетчиков, никогда Enumerator::Lazy.

Я вижу, что Enumerator в Руби 1.9.3 предлагает эту подобную форму #new:

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

К сожалению, этот конструктор был полностью удален в Рубине 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. Как вы думаете?

ответ

7

Я думаю, вы должны вернуть нормальный Enumerator с помощью to_enum:

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, будет ленивым перечислителем ленивым.

+1

Вы просто взорвали мой ум Марк-Андре. Мой код просто перешел от идиотического к идиоматическому. Я не понимал, что Ruby хочет, чтобы мы всегда трафик в Enumerators, а не Enumerator :: Lazy. Где нам нужно что-то быть ленивым, мы просим перечислителя для #lazy версии. Недостатком, возможно, является то, что пользователи наших абстракций действительно должны понимать, когда звонить #lazy (например, перед вызовом #drop (n)). Поверхность - четкий чистый код. –

+0

У меня было так много проблем (и многому чему научился) #drop (n). Теперь, когда я возвращаю «простые» счетчики всюду, мне пришлось побрызгать несколько ... lazy.drop (n) ... о. Поэтому я определил метод, похожий на капли, который просто продвигает Enumerator, позволяя мне изменить его на ... skip (n) ... –

+0

Право. Вы можете определенно определить такой метод, как 'Enumerable # skip (n)', который возвратил бы 'Enumerator' вместо массива типа' drop' и сыграл бы с этим. –