Die Programmiersprache Ruby

Blog| Forum| Wiki  

Inhaltsverzeichnis

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.


puts("a"){p []}

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