2013-04-12 32 views
5

Enumerable#lazy polega na przeliczaniu podając metodę #each. Jeśli Twój przeliczalny nie ma metody #each, nie możesz użyć #lazy. Teraz Kernel#enum_for i #to_enum zapewniają elastyczność, aby określić metodę wyliczenia innego niż #each:Jaki jest najlepszy sposób na zwrócenie Enumerator :: Lazy, gdy klasa nie definiuje #each?

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

Ale #enum_for i przyjaciele zawsze skonstruować zwykły (nie-leniwych) rachmistrzów, nigdy Enumerator::Lazy.

widzę, że Enumerator w Ruby 1.9.3 oferuje ten podobną formę #Nowe:

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

Niestety że konstruktor został całkowicie usunięty w Ruby 2.0. Również nie sądzę, że był kiedykolwiek dostępny na Enumerator::Lazy. Tak więc wydaje mi się, że jeśli mam klasę z metodą, chcę zwrócić leniwy moduł wyliczający dla, jeśli ta klasa nie ma #each, wtedy muszę zdefiniować jakąś klasę pomocniczą, która definiuje #each. Na przykład mam klasę Calendar. To naprawdę nie ma sensu oferować wymieniania każdej daty od początku wszechczasów. Numer #each byłby bezużyteczny. Zamiast tego oferuje metodę, która wylicza (leniwie) od daty rozpoczęcia:

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 

I EachFrom klasy wygląda następująco:

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 

To działa, ale czuje się ciężki. Może powinienem podklasować Enumerator::Lazy i zdefiniować konstruktora takiego, który jest przestarzały z Enumerator. Co myślisz?

Odpowiedz

7

myślę, że należy powrócić normalne Enumerator użyciu 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 

To, co najbardziej Rubiego oczekiwałby. Mimo że jest nieskończoną Enumerable, nadal jest użyteczny, na przykład:

Calendar.new.each_from(1.year.from_now).first(10) # => [...first ten dates...] 

Gdyby rzeczywiście trzeba leniwe wyliczający, mogą wywołać lazy się:

Calendar.new.each_from(1.year.from_now) 
    .lazy 
    .map{...} 
    .take_while{...} 

Jeśli naprawdę chcą Zwróć leniwy moduł wyliczający, możesz zadzwonić pod numer lazy od Ciebie:

# ... 
    def each_from(first) 
    return to_enum(:each_from, first).lazy unless block_given? 
    #... 

Nie polecałbym jednak, ponieważ byłoby to nieoczekiwane (IMO), może być przesadą i będzie mniej wydajne.

Wreszcie, istnieje kilka nieporozumień w swoim pytaniu:

  • Wszystkie metody Enumerable zakładają each, nie tylko lazy.

  • Możesz zdefiniować metodę each, która wymaga parametru, jeśli chcesz i zawiera Enumerable Większość metod Enumerable nie zadziała, ale each_with_index i kilka innych będzie przekazywać argumenty, więc będą one natychmiast dostępne.

  • The Enumerator.new bez bloku nie ma, ponieważ to_enum jest tym, czego należy użyć. Zauważ, że blok pozostaje. Istnieje również konstruktor dla Lazy, ale ma on zacząć od istniejącego Enumerable.

  • Podajesz, że to_enum nigdy nie tworzy leniwego modułu wyliczającego, ale to nie do końca prawda. Enumerator::Lazy#to_enum specjalizuje się w zwracaniu leniwego modułu wyliczającego. Każda metoda użytkownika pod numerem Enumerable, która wywołuje to_enum, spowoduje, że leniwy moduł wyliczający będzie leniwy.

+1

Po prostu rozwaliłaś mój umysł, Marc-André. Mój kod właśnie przeszedł od idiotycznego do idiomatycznego. Nie rozumiałem, że Ruby chce, abyśmy zawsze kręcili w Enumeratorach, a nie Enumerator :: Lazy. Gdziekolwiek potrzebujemy czegoś, aby być leniwym, prosimy o wyliczenie dla wersji #lazy. Minusem może być to, że użytkownicy naszych abstrakcji naprawdę muszą wiedzieć, kiedy zadzwonić #lazy (np. Przed wywołaniem #drop (n)). Plusem jest czysty kod. –

+0

Miałem wiele problemów (i wiele się nauczyłem) #drop (n). Teraz, gdy zwracam "zwykłych" enumeratorów wszędzie musiałem posypać kilka ... lazy.drop (n) ... o. Tak więc zdefiniowałem metodę podobną do kropli, która po prostu przyspiesza działanie modułu wyliczającego, pozwalając mi zmienić te wartości na ... pomiń (n) ... –

+0

Dobrze. Zdecydowanie można zdefiniować taką metodę, jak 'Enumerable # skip (n)', która zwróci 'Enumerator' zamiast tablicy jak' drop' i zagra z tą. –