2013-04-12 11 views
5

Enumerable#lazy si affida al numero enumerabile fornendo un metodo #each. Se il tuo enumerable non ha un metodo #each non è possibile utilizzare #lazy. Ora Kernel#enum_for e #to_enum offrono la flessibilità di specificare un metodo di enumerazione diverso #each:Qual è il modo migliore per restituire un Enumeratore :: Lazy quando la classe non definisce #each?

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

Ma #enum_for e amici costruiscono sempre semplici enumeratori (non pigri), mai Enumerator::Lazy.

vedo che Enumerator in Ruby 1.9.3 offre questa forma simile di #new:

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

Purtroppo questo costruttore è stato completamente rimosso in Ruby 2.0. Inoltre, non penso che sia mai stato disponibile su Enumerator::Lazy. Quindi mi sembra che se ho una classe con un metodo voglio restituire un enumeratore pigro, se quella classe non ha #each allora devo definire qualche classe helper che definisce #each.

Ad esempio, ho una classe Calendar. Non ha senso per me offrire di elencare ogni data dall'inizio di tutti i tempi. Un #each sarebbe inutile. Invece offro un metodo che enumera (pigramente) da una data di inizio:

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 

E che EachFrom classe assomiglia a questo:

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 

funziona ma ci si sente pesante. Forse dovrei sottoclasse Enumerator::Lazy e definire un costruttore come quello deprecato da Enumerator. Cosa ne pensi?

risposta

7

Credo che si dovrebbe restituire una normale Enumerator utilizzando 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 

Questo è ciò che la maggior parte delle Rubysti si aspetterebbe. Anche se è un infinito Enumerable, è ancora utilizzabile, ad esempio:

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

se effettivamente bisogno di un enumeratore pigri, si può chiamare lazy stessi:

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

Se davvero desidera restituire un enumeratore pigro, è possibile chiamare lazy da voi metodo:

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

Non lo consiglierei comunque, dato che sarebbe inaspettato (IMO), potrebbe essere eccessivo e sarà meno performante.

Infine, ci sono un paio di idee sbagliate nella vostra domanda:

  • Tutti i metodi di Enumerable assumono un each, non solo lazy.

  • È possibile definire un metodo each che richiede un parametro se lo si desidera e includere Enumerable. La maggior parte dei metodi di Enumerable non funzionerà, ma each_with_index e un paio di altri invierà gli argomenti in modo che questi siano utilizzabili immediatamente.

  • Il Enumerator.new senza un blocco è andato perché to_enum è quello che si dovrebbe usare. Si noti che il modulo di blocco rimane. C'è anche un costruttore per Lazy, ma è pensato per iniziare da uno Enumerable esistente.

  • Si dichiara che to_enum non crea mai un enumeratore pigro, ma non è completamente vero. Enumerator::Lazy#to_enum è specializzato per restituire un enumeratore pigro. Qualsiasi metodo utente su Enumerable che chiama to_enum manterrà pigro l'enumeratore pigro.

+1

Mi hai appena fatto impazzire Marc-André. Il mio codice passò da idiota a idiomatico. Non ho capito che Ruby vuole che ci occupiamo sempre del traffico in Enumeratori e non Enumeratore :: Lazy. Ovunque sia necessario qualcosa per essere pigri, chiediamo a quell'enumeratore la versione #lazy. Il lato negativo è forse che gli utenti delle nostre astrazioni devono davvero capire quando chiamare #lazy (ad esempio prima di chiamare #drop (n)). Il lato positivo è il codice pulito e nitido. –

+0

Ho avuto così tanti problemi con (e ho imparato tanto da) #drop (n). Ora che sto restituendo "semplici" Enumeratori ovunque ho dovuto cospargere alcuni ... lazy.drop (n) ... circa. Così ho definito un metodo drop-like che fa avanzare semplicemente l'Enumeratore, lasciandomi cambiare a ... skip (n) ... –

+0

Giusto. Si può sicuramente definire un metodo come "Enumerable # skip (n)" che restituisce un 'Enumerator' invece di un array come' drop' fa e gioca con quello. –

Problemi correlati