2016-05-28 22 views
1

Ich lerne über die Magie von Enumerable in Ruby. Ich habe gehört, dass man Enumerable nur einschließen und each Methode implementieren und die Energie von Enumerable für diese Klasse haben kann.implementieren `each` für Enumerable Mixin in Ruby

Also dachte ich über die Implementierung meiner eigenen benutzerdefinierten Klasse Foo für die Praxis. Es sieht aus wie folgt:

class Foo 
    include Enumerable 

    def initialize numbers 
    @num = numbers 
    end 

    def each 
    return enum_for(:each) unless block_given? 
    @num.each { |i| yield i + 1 } 
    end 
end 

Diese Klasse nimmt ein Array und seine each arbeitet fast ähnlich wie Array#each. Hier ist der Unterschied:

>> f = Foo.new [1, 2, 3] 
=> #<Foo:0x00000001632e40 @num=[1, 2, 3]> 
>> f.each { |i| p i } 
2 
3 
4 
=> [1, 2, 3] # Why this? Why not [2, 3, 4]? 

Alles funktioniert wie ich erwarte, außer einer Sache, die die letzte Aussage ist. Ich weiß es ist der Rückgabewert, aber sollte es nicht [2, 3, 4] sein. Gibt es eine Möglichkeit, es [2, 3, 4] zu machen.

Bitte auch Kommentar auf die Art, wie ich each implementiert habe. Wenn es einen besseren Weg gibt, lass es mich wissen. Zuerst in meiner Implementierung hatte ich diese Zeile return enum_for(:each) unless block_given? nicht und dann funktionierte es nicht, wenn kein Block zur Verfügung gestellt wurde. Ich habe mir diese Zeile von irgendwo geliehen und bitte sag mir auch, ob das der richtige Weg ist, um mit der Situation umzugehen oder nicht.

Antwort

2

Der Rückgabewert von each soll der Empfänger sein, das heißt self . Aber Sie geben das Ergebnis des Aufrufs @num.each zurück. Jetzt, wie ich gerade sagte, each gibt self zurück, ergo @num.each gibt @num zurück.

Die Lösung ist einfach: self zurück:

def each 
    return enum_for(:each) unless block_given? 
    @num.each { |i| yield i + 1 } 
    self 
end 

Oder vielleicht ein bisschen mehr Rubyish:

def each 
    return enum_for(:each) unless block_given? 
    tap { @num.each { |i| yield i + 1 }} 
end 

[Eigentlich wie Ruby 1.8.7+, each auch soll ein Enumerator zurückgeben, wenn ohne Block aufgerufen wird, aber Sie behandeln das bereits korrekt. Tipp: Wenn Sie optimierte Versionen einiger der anderen Enumerable Methoden implementieren möchten, indem Sie sie überschreiben, oder Ihre eigenen Enumerable ähnliche Methoden mit ähnlichem Verhalten hinzufügen möchten, wie die ursprünglichen, werden Sie & einfügen genau die gleiche Zeile Immer wieder vergessen Sie den Namen der Methode zu ändern. Wenn Sie die Zeile mit return enum_for(__callee__) unless block_given? ersetzen, müssen Sie sich nicht daran erinnern, den Namen zu ändern.]

2

each ändert kein Array. Wenn Sie modifizierte Array zurückgeben möchten, verwenden Sie map:

def each 
    return enum_for(:each) unless block_given? 
    @num.map { |i| yield i + 1 } 
end 
f.each { |i| p i } 
2 
3 
4 
=> [2, 3, 4] 

Aber ich empfehle jedem in einem benutzerdefinierten Methode zu verwenden. Sie können jedes Element Ihres Arrays in der Methode initialize um 1 erhöhen, da Sie es für alle Berechnungen verwenden möchten. Sie können auch Ihre each-Methode ändern, um die Verwendung von enum_for zu vermeiden, indem Sie block_given? innerhalb eines Blocks übergeben. Schließlich wird Ihr Code wie folgt aussehen:

class Foo 
    include Enumerable 

    def initialize(numbers) 
    @num = numbers.map {|n| n + 1 } 
    end 

    def each 
    @num.each { |i| yield i if block_given? } 
    end 
end 

f = Foo.new [1, 2, 3] 
=> #<Foo:0x00000000f8e0d0 @num=[2, 3, 4]> 
f.each { |i| p i } 
2 
3 
4 
=> [2, 3, 4] 
+0

Mit yield (i + 1) wenn block_given? 'Ich bekomme ein falsches Ergebnis für jeden – Lokesh

+0

@ Lokesh, vielleicht haben Sie verpasst etwas Code. Bitte, siehe aktuelle Version der Antwort – Ilya

+0

Ich mag Ihre erste Lösung. Die zweite Lösung, die Sie vorgeschlagen haben, die zu initialisieren ist, passt nicht in meine Bedürfnisse, denn statt (i + 1) habe ich etwas ('abc' * i), das den Raum einnimmt, also würde @num vorher ändern auf mehr Platz als nötig. – Lokesh

1

Sie müssen map statt each verwenden.

f.map { |i| p i } 
#=> [2,3,4] 

Die Tatsache, dass FooEnumerable umfasst bedeutet, dass alle Methoden der Enumerable können auf Instanzen von Foo

+0

Hallo. Das Problem ist, ich möchte, dass mein benutzerdefiniertes "each" sich wie jedes andere "normal" verhält. – Lokesh

+0

@Lokesh verhält es sich wie ein normales 'each'. Das heißt, Array verhält sich genauso. Ich mache Methodenverkettung möglich. 'map' ist der richtige Weg. – steenslag

0

aufgerufen werden Diese Klasse nimmt einen Array und sein jeder arbeitet fast ähnlich wie Array#each.

Ich weiß es ist der Rückgabewert, aber sollte es nicht sein [2, 3, 4].

  1. A def gibt das Ergebnis der letzten Anweisung, die ausgeführt wurde.

  2. Array#each gibt das ursprüngliche Array zurück.

diese Regeln zu Ihrem def Anwendung:

def each 
    return enum_for(:each) unless block_given? 
    @num.each { |i| yield i + 1 } #Array#each returns @num 
end #When a block is given, the result of the last statement that was executed is @num 

konnte man immer etwas tun, wie folgt aus:

class Foo 
    include Enumerable 

    def initialize numbers 
    @num = numbers 
    @enum_vals = [] 
    end 

    def each 
    if block_given? 
     @num.each do |i| 
     yield i + 1 
     @enum_vals << i + 1 
     end 

     @enum_vals 
    else 
     enum_for 
    end 
    end 
end 

result = Foo.new([1, 2, 3, ]).each {|i| p i} 
p result 

--output:-- 
2 
3 
4 
[2, 3, 4]