Wie funktioniert Enumeratoren in Ruby 1.9.1?
-
07-07-2019 - |
Frage
Diese Frage ist nicht, wie Enumeratoren zu verwenden, in Ruby 1.9.1, sondern ich bin gespannt, wie sie funktionieren. Hier ist ein Code:
class Bunk
def initialize
@h = [*1..100]
end
def each
if !block_given?
enum_for(:each)
else
0.upto(@h.length) { |i|
yield @h[i]
}
end
end
end
In dem obigen Code, den ich e = Bunk.new.each
verwenden kann, und dann e.next
, e.next
jedes aufeinanderfolgende Element zu erhalten, aber wie genau er die Ausführung Aussetzung und dann an der richtigen Stelle wieder aufzunehmen?
Ich bin mir bewusst, dass, wenn die Ausbeute bei der 0.upto
mit Fiber.yield
ersetzt wird, dann ist es leicht zu verstehen, aber das ist hier nicht der Fall. Es ist eine gute alte yield
, so wie funktioniert es?
ich enumerator.c aussehen, aber es ist wiehern für mich unverständlich. Vielleicht Fasern verwendet wird, könnte jemand eine Implementierung in Ruby bieten, nicht 1.8.6 Stil Fortsetzung basierte Enumeratoren, das macht alles klar?
Lösung
Hier ist ein einfacher Ruby-Enumerator, Fasern und soll so ziemlich verhalten sich wie das Original verwendet:
class MyEnumerator
include Enumerable
def initialize(obj, iterator_method)
@f = Fiber.new do
obj.send(iterator_method) do |*args|
Fiber.yield(*args)
end
raise StopIteration
end
end
def next
@f.resume
end
def each
loop do
yield self.next
end
rescue StopIteration
self
end
end
Und bevor jemand beschwert sich über Ausnahmen als Flusskontrolle. Die wirkliche Enumerator wirft StopIteration am Ende auch so ich emuliert nur das ursprüngliche Verhalten
Verbrauch:
>> enum = MyEnumerator.new([1,2,3,4], :each_with_index)
=> #<MyEnumerator:0x9d184f0 @f=#<Fiber:0x9d184dc>
>> enum.next
=> [1, 0]
>> enum.next
=> [2, 1]
>> enum.to_a
=> [[3, 2], [4, 3]]
Andere Tipps
Eigentlich in Ihrer E = Bunk.new.each die else-Klausel wird zunächst nicht ausgeführt. Statt der ‚if! Block_given‘ Klausel ausführt und gibt eine Enumeratorobjekt. Das Enumeratorobjekt tut hält ein Faser Objekt intern. (Zumindest das ist, was es wie in enumerator.c aussieht)
Wenn Sie e.each nennen es wird ein Verfahren auf einem enumerator Aufruf, die intern eine Faser zur Verfolgung ihrer Ausführungskontext zu halten. Diese Methode ruft die Bunk.each Methode, um die Fasern Ausführungskontext verwenden. Der Bunk.each Anruf hier funktioniert die else-Klausel execut und den Wert ergibt auf.
Ich weiß nicht, wie Ausbeute umgesetzt wird oder wie eine Faser des Ausführungskontext verfolgt. Ich habe nicht in diesem Code aussehen. Fast alle der Enumerator und Faser Magie ist in C implementiert.
Sind Sie fragen wirklich, wie Fasern und Ausbeute umgesetzt werden? Welche Details suchen Sie?
Wenn ich weg von der Unterseite bin mir bitte korrigieren.
Wie die anderen Plakate erwähnt, ich glaube, es über eine eigene „Faser“ schafft [1.9]. In 1.8.7 (oder 1.8.6, wenn Sie das updates Juwel verwenden) irgendwie oder andere es tut die gleiche Sache (vielleicht, weil alle Fäden in 1,8 die äquivalent von Fasern sind, verwendet er sich nur?)
So in 1.9 und 1.8.x, wenn Sie von ihnen mehr Ketten zusammen a.each_line.map.each_with_index {}
Es fließt tatsächlich durch diese ganze Kette mit jeder Zeile, wie ein Art von Rohr auf der Befehlszeile
http://pragdave.blogs.pragprog.com /pragdave/2007/12/pipelines-using.html
HTH.
ich denke, das wäre genauer. jeweils auf der enumerator Aufruf sollte die gleiche sein wie die ursprüngliche Iteratormethode aufrufen. So würde ich leicht die ursprüngliche Lösung dieses Problems ändern:
class MyEnumerator
include Enumerable
def initialize(obj, iterator_method)
@f = Fiber.new do
@result = obj.send(iterator_method) do |*args|
Fiber.yield(*args)
end
raise StopIteration
end
end
def next(result)
@f.resume result
end
def each
result = nil
loop do
result = yield(self.next(result))
end
@result
end
end