2013-04-12 11 views
5

Enumerable#lazy verlässt sich auf Ihre Enumerable Bereitstellung einer #each Methode. Wenn Ihr Enumerable keine #each Methode hat, können Sie #lazy nicht verwenden. Jetzt Kernel#enum_for und #to_enum bieten die Flexibilität, eine Aufzählung andere Methode als #each angeben:Was ist der beste Weg, um einen Enumerator :: Lazy zurückzugeben, wenn Ihre Klasse #each nicht definiert?

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

Aber #enum_for und Freunde immer konstruieren Ebene (nicht faul) Aufzählungen, nie Enumerator::Lazy.

Ich sehe, dass Enumerator in Ruby 1.9.3 diese ähnliche Form der #new bietet:

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

Leider, dass Konstruktor wurde in Ruby 2.0 vollständig entfernt. Ich glaube auch nicht, dass es jemals auf Enumerator::Lazy überhaupt verfügbar war. So scheint es mir, dass, wenn ich eine Klasse mit einer Methode habe, ich einen Lazy Enumerator zurückgeben möchte, wenn diese Klasse keine #each hat, dann muss ich eine Hilfsklasse definieren, die #each definiert.

Zum Beispiel habe ich eine Calendar Klasse. Es macht für mich wenig Sinn, jedes Datum von Anfang an aufzuzählen. Ein wäre nutzlos. Stattdessen biete ich eine Methode, die (träge) von einem Startdatum aufzählt:

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 

Und das EachFrom Klasse sieht wie folgt aus:

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 

Es funktioniert, aber es fühlt sich schwer an. Vielleicht sollte ich die Unterklasse Enumerator::Lazy ableiten und einen Konstruktor wie den veralteten von Enumerator definieren. Was denken Sie?

Antwort

7

Ich denke, man sollte eine normale Enumerator mit to_enum zurück:

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 

Dies ist, was die meisten Rubyisten erwarten. Auch wenn es eine unendliche Enumerable ist, ist es immer noch verwendbar, zum Beispiel:

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

Wenn sie tatsächlich einen faulen enumerator benötigen, können sie lazy sich nennen:

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

Wenn Sie wirklich wollen Rückkehr einen faulen enumerator, können Sie lazy von Ihnen Methode aufrufen:

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

Ich würde es jedoch nicht empfehlen, da es unerwartet (IMO) wäre, könnte ein Overkill sein und weniger leistungsfähig sein.

Schließlich gibt es ein paar Missverständnisse in Ihrer Frage:

  • Alle Methoden von Enumerable nehmen eine each, nicht nur lazy.

  • Sie können eine each Methode definieren, die einen Parameter erfordert und Enumerable einschließen. Die meisten Methoden von Enumerable werden nicht funktionieren, aber each_with_index und ein paar andere werden Argumente weiterleiten, so dass diese sofort verwendbar wären.

  • Die Enumerator.new ohne einen Block ist weg, weil to_enum ist, was man verwenden sollte. Beachten Sie, dass die Blockform bleibt. Es gibt auch einen Konstruktor für Lazy, aber es soll von einem vorhandenen Enumerable starten.

  • Sie geben an, dass to_enum nie einen verzögerten Enumerator erstellt, aber das ist nicht ganz richtig. Enumerator::Lazy#to_enum ist spezialisiert, um einen Lazy Enumerator zurückzugeben. Jede Benutzermethode unter Enumerable, die to_enum aufruft, wird einen trägen Enumerator faul halten.

+1

Sie haben gerade meine Meinung Marc-André. Mein Code ging einfach von idiotisch zu idiomatisch. Ich habe nicht verstanden, dass Ruby möchte, dass wir immer mit Enumeratoren und nicht mit Enumerator :: Lazy handeln. Wo immer wir etwas brauchen, um faul zu sein, fragen wir diesen Enumerator für die Version #lazy. Der Nachteil ist vielleicht, dass Benutzer unserer Abstraktionen wirklich verstehen müssen, wann sie #lazy aufrufen müssen (z. B. bevor sie #drop (n) aufrufen). Die Oberseite ist sauberer sauberer Code. –

+0

Ich hatte so viel Probleme mit (und lernte so viel) #drop (n). Jetzt, wo ich "normale" Aufzählungszeichen zurückgebe, musste ich überall ein paar ... faule.drop (n) ... herum streuen. Also habe ich eine Drop-ähnliche Methode definiert, die den Enumerator einfach weiterleitet und mir erlaubt, diese zu ... skip (n) zu ändern ... –

+0

Richtig. Sie können definitiv eine Methode wie 'Enumerable # skip (n)' definieren, die anstelle eines Arrays wie 'drop' einen' Enumerator' zurückgibt und damit spielt. –

Verwandte Themen