Allgemein
Iteratoren sind eine spezielle Art von Methoden, sie sind eines der populärsten Ruby-Elemente. Iteratoren machen Code dynamisch, er muss nicht zwangsläufig immer genau das eine tun, was ihm zugeordnet wurde. So ist es möglich, die Art, wie eine Methode ausgeführt wird, zur Laufzeit zu verändern. Gibt man beispielsweise Array#sort einen Codeblock mit, verändert die Methode ihr Verhalten. Der Block, der einer Iteratormethode mitgegeben wird, wird gelegentlich als Closure bezeichnet und kann durch die Ruby-Klasse Proc beschrieben werden (siehe weiter unten). Wenn einer Methode ein Codeblock mitgegeben wird, "mutiert" sie zum Iterator.
ist also ein Iterator? Nein, nicht ganz, obwohl die Syntax korrekt ist (man kann grundsätzlich jeder Methode einen Codeblock mitgeben), ist diese Methode kein Iterator, denn der Codeblock wird nicht genutzt.
|
[1, 2, 3].each{|e| puts e} |
ist damit ein Iterator.
Definieren von Iteratoren
Iteratoren werden wie jede andere Methode definiert:
1
2
3
4
5
6
7
|
class Array
def reverse_with_index
self.reverse.each_with_index do |e, i|
yield(e, i)
end
end
end |
In Iteratormethoden taucht meist das Schlüsselwort yield auf. Immer wenn yield aufgerufen wird, wird der Codeblock ausgeführt. Alle Argumente, die yield mitgegeben werden, tauchen in der fertigen Methode als Blockargumente in |..| auf:
1
2
3
|
[3, 2, 1].reverse_with_index do |element, index|
#Die Argumente zwischen |..| sind die, die in der Methodendefinition yield übergeben wurden
end |
Was ist aber, wenn einem Iterator kein Codeblock mitgegeben wird?
|
[3, 2, 1].reverse_with_index #=> LocalJumpError: No block given |
Ruby wirft einen Error. Um das zu verhindern, kann man mit der Methode block_given? aus dem Kernel-Modul feststellen, ob ein Codeblock da ist:
1
2
3
4
5
6
7
8
9
|
class Array
def reverse_with_index
if block_given?
self.reverse.each_with_index{|e, i| yield(e, i)}
else
puts "No block given!"
end
end
end |
Man kann den einer Methode mitgegebenen Codeblock auch einer Variablen zuweisen:
1
2
3
4
5
6
7
8
9
10
11
12
|
class A
def initialize(&block)
@block = block
end
def do_it(arg = nil)
@block.call(arg) #Man kann call auf die gleiche Art und Weise wie yield Argumente übergeben.
end
end
x = A.new{|wort| puts "Hello, #{wort}!"}
x.do_it("world")
#=> "Hello, world!" |
Das &-Zeichen (auch Ampersand oder kaufmännisches Und) zeigt Ruby hierbei an, dass die Variable block den Codeblock aufnehmen soll, es wird als Proc-Objekt gespeichert. So kann man ihn auch später noch anwenden.
Schleifen
Auch Schleifen sind Iteratoren, obwohl man daran eigentlich gar nicht denkt, denn man kann bei ihnen das do-Schlüsselwort (bzw. {}) weglassen:
1
2
3
4
5
6
7
8
9
10
11
|
x = 0
while x > 9 [do]
#Code...
end
until x < 9 [do]
#Code...
end
x = [1, 2, 3]
for i in x [do]
#Code...
end |
Beim loop-Iterator kann man do allerdings nicht weglassen:
1
2
3
4
5
6
7
8
9
|
#Falsch:
loop
#Code...
end
#=> LocalJumpError: No block given
#Korrekt:
loop do
#Code...
end |
Änderungen an Iteratoren zu Ruby 1.9.1
- Viele Iteratormethoden werfen nun keinen Error mehr, falls sie ohne Block aufgerufen werden. Stattdessen geben sie ein Enumerator-Objekt zurück.
|
puts [1, 2, 3].each.class #=> Enumerator |
Siehe auch