class Enumerator

Eine Klasse, die sowohl interne als auch externe Iteration ermöglicht.

Ein Enumerator kann mit den folgenden Methoden erstellt werden.

Die meisten Methoden haben zwei Formen: eine Blockform, bei der der Inhalt für jedes Element der Enumeration ausgewertet wird, und eine Nicht-Blockform, die einen neuen Enumerator zurückgibt, der die Iteration umschließt.

enumerator = %w(one two three).each
puts enumerator.class # => Enumerator

enumerator.each_with_object("foo") do |item, obj|
  puts "#{obj}: #{item}"
end

# foo: one
# foo: two
# foo: three

enum_with_obj = enumerator.each_with_object("foo")
puts enum_with_obj.class # => Enumerator

enum_with_obj.each do |item, obj|
  puts "#{obj}: #{item}"
end

# foo: one
# foo: two
# foo: three

Dies ermöglicht es Ihnen, Enumeratoren miteinander zu verketten. Sie können beispielsweise die Elemente einer Liste in Zeichenketten abbilden, die den Index und das Element als Zeichenkette enthalten, über

puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
# => ["0:foo", "1:bar", "2:baz"]

Externe Iteration

Ein Enumerator kann auch als externer Iterator verwendet werden. Zum Beispiel gibt Enumerator#next den nächsten Wert des Iterators zurück oder löst StopIteration aus, wenn der Enumerator am Ende ist.

e = [1,2,3].each   # returns an enumerator object.
puts e.next   # => 1
puts e.next   # => 2
puts e.next   # => 3
puts e.next   # raises StopIteration

next, next_values, peek und peek_values sind die einzigen Methoden, die externe Iteration verwenden (und Array#zip(Enumerable-not-Array), die intern next verwendet).

Diese Methoden beeinflussen keine anderen internen Enumerationsmethoden, es sei denn, die zugrunde liegende Iterationsmethode selbst hat Nebeneffekte, z. B. IO#each_line.

FrozenError wird ausgelöst, wenn diese Methoden gegen einen gefrorenen Enumerator aufgerufen werden. Da rewind und feed auch den Zustand für die externe Iteration ändern, können diese Methoden ebenfalls FrozenError auslösen.

Externe Iteration unterscheidet sich signifikant von interner Iteration, da sie eine Fiber verwendet

Konkret

Thread.current[:fiber_local] = 1
Fiber[:storage_var] = 1
e = Enumerator.new do |y|
  p Thread.current[:fiber_local] # for external iteration: nil, for internal iteration: 1
  p Fiber[:storage_var] # => 1, inherited
  Fiber[:storage_var] += 1
  y << 42
end

p e.next # => 42
p Fiber[:storage_var] # => 1 (it ran in a different Fiber)

e.each { p _1 }
p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber)

Externe Iteration in interne Iteration umwandeln

Sie können einen externen Iterator verwenden, um einen internen Iterator wie folgt zu implementieren

def ext_each(e)
  while true
    begin
      vs = e.next_values
    rescue StopIteration
      return $!.result
    end
    y = yield(*vs)
    e.feed y
  end
end

o = Object.new

def o.each
  puts yield
  puts yield(1)
  puts yield(1, 2)
  3
end

# use o.each as an internal iterator directly.
puts o.each {|*x| puts x; [:b, *x] }
# => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

# convert o.each to an external iterator for
# implementing an internal iterator.
puts ext_each(o.to_enum) {|*x| puts x; [:b, *x] }
# => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3