Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Ausdrücke



Bis jetzt waren wir ordentlich rücksichtlos, was unsere Verwendung von Ausdrücken in Ruby anging. Immerhin ist a=b+c ziemlicher Standard. Sie könnten eine ganze Menge Code in Ruby schreiben, ohne etwas von diesem Kapitel gelesen zu haben.

Aber es würde nicht so viel Spaß machen;-).

Einer der ersten Unterschiede von Ruby ist, dass alles, was vernünftig einen Wert zurückgeben kann, das auch tut: fast alles ist ein Ausdruck. Was bedeutet das in der Praxis?

Zu den offensichtlichen Dingen gehört die Möglichkeit, Anweisungen zu verketten.

a = b = c = 0 » 0
[ 3, 1, 7, 0 ].sort.reverse » [7, 3, 1, 0]

Weniger offensichtlich ist vielleicht, dass Dinge, die in C oder Java normalerweise Anweisungen sind, in Ruby Ausdrücke sind. if und case zum Beispiel geben beide den Wert des zuletzt ausgeführten Ausdrucks zurück.

songType = if song.mp3Type == MP3::Jazz
             if song.written < Date.new(1935, 1, 1)
               Song::TradJazz
             else
               Song::Jazz
             end
           else
             Song::Other
           end

 rating = case votesCast           when 0...10    then Rating::SkipThisOne           when 10...50   then Rating::CouldDoBetter           else                Rating::Rave           end

Wir sprechen ab Seite 81 mehr über if und case.

Operatorenausdrücke (Operator Expressions)

Ruby hat die grundlegenden Operatoren (+, -, *, / und so weiter) und verbirgt einige wenige Überraschungen. Ein komplette Liste der Operatoren und ihrer Vorrangsregeln finden Sie in Tabelle 18.4 auf Seite 221.

In Ruby sind viele Operatoren eigentlich Methodenaufrufe. Wenn Sie a*b+c schreiben, fordern Sie eigentlich das Objekt, das a referenziert, auf, die Methode ``*'' auszuführen und übergeben dabei b als Parameter. Dann fordern Sie das aus der Berechnung resultierende Objekt auf, ``+'' auszuführen, wobei c der Parameter ist. Das entspricht dem Ausdruck

(a.*(b)).+(c)

Da alles ein Objekt ist und da Sie Instanzmethoden umdefinieren können, können Sie die Grundrechnungsarten immer umdefinieren, wenn Ihnen die Antworten, die Sie bekommen, nicht gefallen.

class Fixnum
  alias oldPlus +
  def +(other)
    oldPlus(other).succ
  end
end
1 + 2 » 4
a = 3
a += 4 » 8

Nützlicher ist die Tatsache, dass die Klassen, die Sie schreiben, Operatorenausdrücke verwenden können, als ob sie eingebaute Objekte wären. Zum Beispiel könnte es sein, dass wir einige Sekunden Musik aus der Mitte eines Liedes extrahieren möchten. Dazu könnten wir den Indexoperator ``[]'' verwenden, um die zu extrahierende Musik anzugeben.

class Song
  def [](fromTime, toTime)
    result = Song.new(self.title + " [extract]",
                      self.artist,
                      toTime - fromTime)
    result.setStartTime(fromTime)
    result
  end
end

Dieses Codefragment erweitert die Klasse Song um die Methode ``[]'', die zwei Parameter verlangt (eine Start- und eine Endzeit). Sie gibt einen neuen Song mit der im Intervall angegebenen Musik zurück. Wir könnten dann die Einleitung eines Songs mit Code wie folgendem spielen:

aSong[0, 0.15].play

Verschiedene Ausdrücke

Neben den offensichtlichen Operatorenausdrücken und Methodenaufrufen und den (vielleicht) weniger offensichtlichen Anweisungsausdrücken (wie if und case), gibt es in Ruby einige Dinge mehr, die Sie in Ausdrücken verwenden können.

Befehlsexpansion

Wenn Sie einen String in Backquotes (`) angeben oder den %x-Begrenzer benutzen, wird dieser (wenn nicht anders angegeben) als Befehl des darunterliegenden Betriebssystems ausgeführt. Der Wert des Ausdrücks ist die Standardausgabe des Befehls. Newlines werden nicht abgeschnitten, also ist es wahrscheinlich, dass am Wert, den Sie zurückbekommen, noch ein Newline oder ein Zeilenvorschub hängt.

`date` » "Sun Mar  4 23:23:52 CST 2001\n"
`dir`.split[34] » "lib_pstore.txi"
%x{echo "Hello there"} » "Hello there\n"

Sie können Ausdrucksexpansion und alle üblichen Escapesequenzen im Befehlsstring verwenden.

for i in 0..3
  status = `dbmanager status id=#{i}`
  # ...
end

Durch die globale Variable $? können Sie auf den Exitstatus des Programms zugreifen.

Backquotes sind weich

In der Beschreibung des Befehlsausgabeausdrucks sagten wir, dass Strings in Backquotes als Befehle ausgegeben werden, ``wenn nicht anders angegeben''. Tatsächlich wird der String der Methode Kernel::` (ein einzelner Backquote) übergeben. Wenn Sie möchten, können Sie diese Methode überschreiben.

alias oldBackquote `
def `(cmd)
  result = oldBackquote(cmd)
  if $? != 0
    raise "Command #{cmd} failed"
  end
  result
end
print `date`
print `data`
produces:
Sun Mar  4 23:23:52 CST 2001
prog.rb:3: command not found: data
prog.rb:5:in ``': Command data failed (RuntimeError)
	from prog.rb:10

Zuweisung

So ziemlich jedes Beispiel, das wir bis jetzt in diesem Buch gegeben haben, hat Zuweisungen beinhaltet. Vielleicht wird es Zeit, etwas darüber zu sagen.

Eine Zuweisung weist die Variable oder das Attribut an seiner linken Seite (den L-Wert) an, auf den Wert an seiner rechten Seite (der R-Wert) zu zeigen. Dann gibt es diesen Wert als Ergebnis des Zuweisungsausdrucks zurück. Das bedeutet, dass Sie Zuweisungen nicht nur verketten, sondern auch an einigen unerwarteten Stellen ausführen können.

a = b = 1 + 2 + 3
a » 6
b » 6
a = (b = 1 + 2) + 3
a » 6
b » 3
File.open(name = gets.chomp)

In Ruby gibt es zwei grundlegende Arten von Zuweisung. Die Erste weist einer Variablen oder einer Konstanten eine Objektreferenz zu. Diese Form der Zuweisung ist fest in der Sprache verdrahtet.

instrument = "piano"
MIDDLE_A   = 440

Bei der zweiten Art ist der L-Wert ein Objektattribut oder eine Referenz auf eine Element:

aSong.duration    = 234
instrument["ano"] = "ccolo"

Diese Zuweisungen ist besonders, weil sie durch das Aufrufen von Methoden des L-Werts implementiert sind. Das bedeutet, dass Sie sie überschreiben können.

Wir haben bereits gesehen, wie man eine beschreibbares Objektattribut erzeugt. Definieren Sie einfach einen Methodennamen, der mit einem Istgleichzeichen endet. Diese Methode erhält den R-Wert der Zuweisung als ihren Parameter.

class Song
  def duration=(newDuration)
    @duration = newDuration
  end
end

Es gibt kein Grund, dass diese attributsetzenden Methoden internen Instanzvariablen entsprechen müssen, oder dass jedes schreibbare Attribut auch lesbar sein muss (oder umgekehrt).

class Amplifier
  def volume=(newVolume)
    self.leftChannel = self.rightChannel = newVolume
  end
  # ...
end

Sidebar: Accessors innerhalb einer Klasse benutzen

Warum haben wir im Beispiel auf Seite 76self.leftChannel geschrieben? Nun, da ist ein verstecktes Gotcha[Von ``got you'', in etwa ``hab' ich dich''. Wird oft als Bezeichnung für unerwartete Eigenschaften einer Sprache verwendet, Anm. d. Übers.] bei beschreibbaren Attributen. Normalerweise können Methoden innerhalb einer Klasse andere Methoden der selben Klasse und ihrer Superklasse in funktionioneller Form (d.h. mit implizitem Empfänger self) aufrufen. Bei attribute writers funktioniert das jedoch nicht. Ruby sieht die Zuweisung und nimmt an, dass der Name links von ihr eine lokale Variable sein muss, kein Methodenaufruf.
class BrokenAmplifier
  attr_accessor :leftChannel, :rightChannel
  def volume=(vol)
    leftChannel = self.rightChannel = vol
  end
end
ba = BrokenAmplifier.new
ba.leftChannel = ba.rightChannel = 99
ba.volume = 5
ba.leftChannel » 99
ba.rightChannel » 5

Da wir vergessen haben, ``self.'' vor die Zuweisung zu leftChannel zu schreiben, speicherte Ruby den neuen Wert in einer lokalen Variable der Methode volume=; das Attribut des Objekts wurde nie aktualisiert. Das Aufstöbern dieses Bugs kann knifflig werden.

Parallelzuweisung

Während Ihrer zweiten Woche in einem Programmierkurs (oder dem zweiten Semester, wenn es an einer party school war) mussten Sie wohl Code schreiben, der die Werte zweier Variablen austauscht:

int a = 1;
int b = 2;
int temp;

temp = a; a = b; b = temp;

Sie können das in Ruby viel sauberer machen:

a, b = b, a

Zuweisungen werden in Ruby effektiv parallel ausgeführt, daher sind die Werte, die zugewiesen werden von der Zuweisung selbst nicht beeinträchtigt. Die Werte an der rechten Seite werden in der Reihenfolge, in der sie auftreten, ausgewertet, noch bevor irgendeine Zuweisung auf die Variablen oder Attribute an der linken Seite durchgeführt wird. Ein etwas gekünsteltes Beispiel veranschaulicht das. Die zweite Zeile weist den Variablen a, b und c die Werte der Ausdrücke x, x+=1 bzw. x+=1 zu.

x = 0 » 0
a, b, c   =   x, (x += 1), (x += 1) » [0, 1, 2]

Wenn eine Zuweisung mehr als einen L-Wert hat, gibt der Zuweisungsausdruck ein Array der R-Werte zurück. Wenn eine Zuweisung mehr L-Werte als R-Werte hat, werden die übriggebliebenen L-Werte auf nil gesetzt. Wenn eine Mehrfachzuweisung mehr R-Werte als L-Werte hat, werden die übriggebliebenen R-Werte ignoriert. In Ruby 1.6.2 werden, wenn eine Zuweisung einen L-Wert und mehrere R-Werte hat, die R-Werte in ein Array konvertiert, das dem L-Wert zugewiesen wird.

Sie können mit Rubys Parallelzuweisungsoperator Arrays expandieren und zusammenklappen. Wenn der letzte L-Wert mit einem Stern beginnt, werden alle übriggebliebenden R-Werte gesammelt und diesem L-Wert als Array zugewiesen. Gleichfalls können Sie auch dem letzten R-Wert, wenn er ein Array ist, einen Stern voranstellen, und ihn so in seine einzelnen Werte zerlegen. (Das ist nicht notwenig, wenn der R-Wert das einzige Ding an der rechten Seite ist -- in diesem Fall wird das Array automatisch expandiert).

a = [1, 2, 3, 4]
b,  c = a » b == 1, c == 2
b, *c = a » b == 1, c == [2, 3, 4]
b,  c = 99,  a » b == 99, c == [1, 2, 3, 4]
b, *c = 99,  a » b == 99, c == [[1, 2, 3, 4]]
b,  c = 99, *a » b == 99, c == 1
b, *c = 99, *a » b == 99, c == [1, 2, 3, 4]

Verschachtelte Zuweisungen

Parallelzuweisungen bieten eine weitere Möglichkeit, die es wert ist, erwähnt zu werden. Die linke Seite einer Zuweisung kann eingeklammerte Listen von Termen enthalten. Ruby behandelt diese Terme, als ob sie ein verschachtelter Zuweisungsausdruck wären. Dabei werden die entsprechenden R-Werte extrahiert und den eingeklammerten Termen zugewiesen, bevor mit den übrigen Zuweisungen weitergemacht wird.

b, (c, d), e = 1,2,3,4 » b == 1, c == 2, d == nil, e == 3
b, (c, d), e = [1,2,3,4] » b == 1, c == 2, d == nil, e == 3
b, (c, d), e = 1,[2,3],4 » b == 1, c == 2, d == 3, e == 4
b, (c, d), e = 1,[2,3,4],5 » b == 1, c == 2, d == 3, e == 5
b, (c,*d), e = 1,[2,3,4],5 » b == 1, c == 2, d == [3, 4], e == 5

Andere Formen von Zuweisung

Wie viele anderen Sprachen auch, hat Ruby eine syntaktische Abkürzung: a=a+2 kann als a+=2 geschrieben werden.

Die zweite Form wird intern in die erste umgewandelt. Das bedeutet, dass Operatoren, die Sie als Methoden in Ihren eigenen Klassen definiert haben, wie erwartet funktionieren:

class Bowdlerize
  def initialize(aString)
    @value = aString.gsub(/[aeiou]/, '*')
  end
  def +(other)
    Bowdlerize.new(self.to_s + other.to_s)
  end
  def to_s
    @value
  end
end
a = Bowdlerize.new("damn ") » d*mn
a += "shame" » d*mn sh*m*

Bedingte Ausführung

Ruby hat mehrere verschiedene Mechanismen für bedingte Ausführung von Code; die meisten sollten sich vertraut anfühlen und viele ermöglichen einige nette Tricks. Bevor wir sie uns näher ansehen müssen wir trotzdem etwas Zeit für boolsche Ausdrücke aufwenden.

Boolsche Ausdrücke

Ruby hat eine einfache Definition für Wahrheit. Jeder Wert, der nicht nil oder die Konstante false ist, ist wahr. Sie werden bemerken, dass die Bibliotheksroutinen diese Tatsache konsequent benutzen. Die Methode IO#gets zum Beispiel, die die nächste Zeile einer Datei zurückgibt, gibt am Ende der Datei nil zurück und ermöglicht es Ihnen Schleifen wie die folgende zu schreiben:

while line = gets
  # process line
end

Für C, C++ und Perl Programmierer jedoch ist hier eine Falle. Die Zahl Null wird nicht als falsch interpretiert. Auch kein String der Länge Null. Es kann zäh werden, seine Gewohnheiten zu ändern.

Defined?, And, Or, und Not

Ruby unterstützt all die gewöhnlichen boolschen Operatoren und führt den neuen Operator defined? ein.

Sowohl ``and'' als auch &&'' geben true zurück, wenn beide Operanden true sind. Sie werten den zweiten Operanden nur aus, wenn der erste true ist (das wird manchmal ``Kurzschlussevaluierung'' genannt). Der einzige Unterschied der beiden Formen ist der Vorrang (``and'' bindet schwächer als ``&&'').

Gleichermaßen geben sowohl ``or'' als auch ``||'' true zurück, wenn einer der Operanden true ist. Sie werten ihren zweiten Operanden nur aus, wenn der erste false ist. Wie auch bei ``and'' ist ihr Vorrang der einzige Unterschied zwischen ``or'' und ``||''.

Nur um das Leben interessant zu machen haben ``and'' und ``or'' gleichen Vorrang, während ``&&'' höheren Vorrang als ``||'' hat.

``not'' und ``!'' geben das Gegenteil ihres Operanden zurück (false, wenn der Operand true ist und true, wenn er false ist). Und ja, ``not'' und ``!'' unterscheiden sich nur im Vorrang.

Die Vorrangregeln werden in Tabelle 18.4 auf Seite 221 zusammengefasst.

Der Operator defined? gibt nil zurück, wenn sein Argument (das ein beliebige Ausdruck sein kann) nicht definiert ist, ansonsten gibt er die Beschreibung dieses Arguments zurück. Wenn das Argument yield ist, gibt defined? den String ``yield'' zurück, wenn ein Codeblock mit dem aktuellen Kontext assoziiert ist.

defined? 1 » "expression"
defined? dummy » nil
defined? printf » "method"
defined? String » "constant"
defined? $& » "$&"
defined? $_ » "global-variable"
defined? Math::PI » "constant"
defined? ( c,d = 1,2 ) » "assignment"
defined? 42.abs » "method"

Zusätzlich zu den boolschen Operatoren unterstützen Ruby-Objekte mit den Methoden ==, ===, <=>, =~, eql? und equal? auch Vergleiche (siehe Tabelle 7.1 auf Seite 81). Alle außer <=> sind in der Klasse Object definiert, werden aber von davon abgeleiteten Klassen oft überschrieben, um sie mit für die Klasse passenden Bedeutungen zu versehen. Die Klasse Array beispielsweise definiert == so um, dass zwei Arrays gleich sind, wenn sie die gleiche Anzahl an Elementen haben und auch die entsprechenden Elemente gleich sind.

Allgemeine Vergleichsoperatoren
Operator Bedeutung
== Test auf gleichen Wert.
=== Wird verwendet, um Gleichheit innerhalb einer when-Bedingung einer case-Anweisung zu testen.
<=> Allgemeiner Vergleichsoperator. Retourniert -1, 0 oder +1, je nachdem, ob der Empfänger kleiner, gleich oder größer als sein Argument ist.
<, <=, >=, > Vergleichsoperatoren für kleiner, kleiner gleich, gleich, größer gleich und größer.
=~ Mustererkennung in einem regulären Ausdruck.
eql? Wahr, wenn der Empfänger und das Argument beide vom gleichen Typ sind und den gleichen Wert haben. 1 == 1.0 ergibt true, 1.eql?(1.0) aber false.
equal? Wahr, wenn der Empfänger und das Argument die gleiche Objekt-ID haben.

Sowohl == als auch =~ haben negierte Formen, != und !~. Diese werden jedoch von Ruby konvertiert, wenn Ihr Programm gelesen wird. a!=b ist äquivalent zu !(a==b) und a!~b ist das gleiche wie !(a=~b). Das bedeuted, dass Sie, wenn Sie eine Klasse schreiben, die == oder =~ überschreibt, gleichzeitig != und !~ gratis dazubekommen. Die Kehrseite der Medaille ist, dass Sie != und =~ nicht unabhängig von == und =~ definieren können.

Sie können Bereiche als boolsche Ausdrücke verwenden. Ein Bereich wie exp1..exp2 wird als false ausgewertet bis exp1 true wird. Der Range wird dann als true ausgewertet, bis exp2 true wird. Ist das geschehen wird der Bereich zurückgesetzt und ist bereit, nochmals abgefeuert zu werden. Wir zeigen dazu einige Beispiele auf Seite 84.

Schlussendlich können Sie einen bloßen regulären Ausdruck als boolschen Ausdruck verwenden. Ruby expandiert ihn zu $_=~/re/.

If und Unless

Ein if-Ausdruck in Ruby ist ziemlich gleich zu einer ``if''-Anweisung in anderen Sprachen.

if aSong.artist == "Gillespie" then
  handle = "Dizzy"
elsif aSong.artist == "Parker" then
  handle = "Bird"
else
  handle = "unknown"
end

Wenn Sie Ihre if-Anweisung auf mehrere Zeilen aufspalten, können Sie das Schlüsselwort then weglassen.

if aSong.artist == "Gillespie"
  handle = "Dizzy"
elsif aSong.artist == "Parker"
  handle = "Bird"
else
  handle = "unknown"
end

Wenn Sie Ihren Code jedoch straffer aufschreiben, ist then notwendig, um den boolschen Ausdruck von den folgenden Anweisungen zu trennen.

if aSong.artist == "Gillespie" then  handle = "Dizzy"
elsif aSong.artist == "Parker" then  handle = "Bird"
else  handle = "unknown"
end

Sie können null oder mehr elsif-Bedingungen und eine optionale else-Bedingung verwenden.

Wie wir vorher gesagt haben, ist if ein Ausdruck und keine Anweisunge -- es gibt einen Wert zurück. Sie müssen den Wert eines if-Ausdrucks nicht verwenden, es kann aber nützlich werden.

handle = if aSong.artist == "Gillespie" then
           "Dizzy"
         elsif aSong.artist == "Parker" then
           "Bird"
         else
           "unknown"
         end

Ruby hat auch eine negierte Form von if:

unless aSong.duration > 180 then
  cost = .25
else
  cost = .35
end

Schlussendlich unterstützt Ruby für alle C-Fans auch den C-Stil von bedingten Ausdrücken:

cost = aSong.duration > 180 ? .35 : .25

Der bedingte Ausdruck gibt entwender den Wert des Ausdrucks vor oder den des Ausdrucks nach dem Doppelpunkt zurück, abhängig davon, ob der boolsche Ausdruck vor dem Fragezeichen zu true oder false ausgewertet wird. In diesem Fall gibt der Ausdruck, wenn der Song länger als 3 Minuten dauert, .35 zurück. Für kürzere Songs gibt er .25 zurück. Wie auch immer das Ergebnis ausfällt, es wird cost zugewiesen.

Modifikatoren für If und Unless

Ruby hat ein nettes Feature mit Perl gemeinsam. Anweisungsmodifikatoren erlauben es Ihnen, Bedingungsanweisung am Ende einer normalen Anweisung zu setzen.

mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/
puts "a = #{a}" if fDebug
print total unless total == 0

Bei einem if-Modifikator wird die vorangegangene Anweisung nur ausgewertet, wenn die Bedingung wahr ist. unless arbeitet umgekehrt.

while gets
  next if /^#/            # Kommentare überspringen
  parseLine unless /^$/   # Leere Zeilen nicht bearbeiten
end

Da if selbst ein Ausdruck ist, kann das zu äußerst obskuren Anweisungen wie folgende führen:

if artist == "John Coltrane"
  artist = "'Trane"
end unless nicknames == "no"

Dieser Weg führt zu den Toren des Wahnsinns.

Case Ausdrücke

Rubys case-Ausdruck ist ein mächtiges Biest: ein mehrfaches if auf Steroiden.

case inputLine

  when "debug"     dumpDebugInfo     dumpSymbols

  when /p\s+(\w+)/     dumpVariable($1)

  when "quit", "exit"     exit

  else     print "Illegal command: #{inputLine}" end

Wie auch if, gibt case den Wert des zuletzt ausgeführten Ausdrucks zurück. Weiters müssen Sie auch hier then setzen, wenn der Ausdruck in der selben Zeile wie die Bedingung steht.

kind = case year
         when 1850..1889 then "Blues"
         when 1890..1909 then "Ragtime"
         when 1910..1929 then "New Orleans Jazz"
         when 1930..1939 then "Swing"
         when 1940..1950 then "Bebop"
         else                 "Jazz"
       end

case vergleich das Ziel (der Ausdruck nach dem Schlüsselwort case) mit jedem der Vergleichsausdrücke nach den Schlüsselwörtern when. Dieser Test wird mit Vergleich === Ziel durchgeführt. Solange eine Klasse eine sinnvolle Bedeutung für === definiert (und das tun alle eingebauten Klassen), können Objekte dieser Klasse in case-Ausdrücken verwendet werden.

Reguläre Ausdrücke zum Beispiel definieren === als simple Mustererkennung.

case line
  when /title=(.*)/
    puts "Title is #$1"
  when /track=(.*)/
    puts "Track is #$1"
  when /artist=(.*)/
    puts "Artist is #$1"
end

Ruby-Klassen sind Instanzen der Klasse Class, die === als Test, ob das Argument eine Instanz der Klasse oder eine ihrer Superklassen ist, definiert. Somit können Sie (wenn Sie die Vorteile von Polymorphismus aufgeben und die Götters des Neuschreibens heraufbeschwören wollen) die Klasse eines Objekts überprüfen:

case shape
  when Square, Rectangle
    # ...
  when Circle
    # ...
  when Triangle
    # ...
  else
    # ...
end

Schleifen

Erzählen Sie's niemandem, aber Ruby hat ziemlich primitive eingebaute Schleifenkonstrukte.

Die while-Schleife führt ihren Körper null oder mehrere Male aus, solange ihre Bedingung wahr ist. Dieses häufige Idiom zum Beispiel liest die Eingabe so lange, bis sie ausgeschöpft ist.

while gets
  # ...
end

Es gibt auch eine negierte Form, die den Körper ausführt, bis die Bedingung wahr wird.

until playList.duration > 60
  playList.add(songList.pop)
end

Wie auch if und unless, können beide Schleifen als Anweisungsmodifikatoren benutzt werden.

a *= 2 while a < 100
a -= 10 until a < 100

Auf Seite 80, im Abschnitt über boolsche Ausdrücke, sagten wir, dass ein Bereich als eine Art Kippschaltung benutzt werden kann und true wird, wenn ein Ereignis eintritt und solange true bleibt, bis ein zweites Ereignis eintritt. Diese Möglichkeit wird normalerweise innerhalb von Schleifen benutzt. Im folgenden Beispiel lesen wir eine Textdatei ein, die die ersten zehn Ordinalzahlen (``erstes'', ``zweites'', etc.) enthält, geben die Zeilen aber erst beginnend mit der einen, die ``drittes'' enthält aus und enden wieder mit der Zeile, die ``fünftes'' enthält.

file = File.open("ordinal")
while file.gets
  print  if /drittes/ .. /fuenftes/
end
produces:
drittes
viertes
fuenftes

Die Elemente eines Bereich, der in einem boolschen Ausdruck gebraucht wird, können selbst wieder Ausdrücke sein. Diese werden jedesmal, wenn der umfassende boolsche Ausdruck ausgewertet wird, auch ausgewertet. Folgender Code zum Beispiel benutzt die Tatsache, dass die Variable $. die Zeilennummer der aktuellen Eingabezeile enthält, um die Zeilennummern eins bis drei und die zwischen einem Match von /ach/ und /neu/ auszugeben.

file = File.open("ordinal")
while file.gets
  print if ($. == 1) || /ach/ .. ($. == 3) || /neu/
end
erzeugt:
erstes
zweites
drittes
achtes
neuntes

Bei while- und if-Modifikatoren gibt es einen Kniff: Wenn der Ausdruck, den sie modifizieren ein begin/end Block ist, wird der Code im Block immer mindestens ein Mal ausgeführt, unabhängig vom Wert des boolschen Ausdrucks.

print "Hello\n" while false
begin
  print "Goodbye\n"
end while false
produces:
Goodbye

Iteratoren

Wenn Sie den Anfang des vorigen Abschnitts gelesen haben, könnten Sie abgeschreckt worden sein. ``Ruby hat ziemlich primitive eingebaute Schleifenkonstrukte'' stand da. Verzweifeln Sie nicht, geneigter Leser, es gibt gute Neuigkeiten. Ruby braucht keine ausgereiften eingebauten Schleifen, weil alles, was Spaß macht, mit Iteratoren implementiert ist.

Ruby hat zum Beispiel keine ``for''-Schleife -- zumindest nicht die Art davon, die Sie in C, C++ und Java finden. Stattdessen verwendet Ruby Methoden, die in verschiedenen eingebauten Klassen definiert sind, um äquivalente, aber weniger fehleranfällige Funktionalität bereitzustellen.

Schauen wir uns einige Beispiele an.

3.times do
  print "Ho! "
end
ergibt:
Ho! Ho! Ho!

Es ist einfach, Zaunpfosten- und Um-eins-vorbei-Fehler zu vermeiden; diese Schleife wird drei Mal ausgeführt, Punkt. Zusätzlich zu times können Sie mit Integers mittels downto, upto und step Schleifen über Bereiche anlegen. Eine traditionelle ``for''-Schleife, die von 0 bis 9 läuft (sowas wie i=0; i < 10; i++), wird wie folgt geschrieben.

0.upto(9) do |x|
  print x, " "
end
ergibt:
0 1 2 3 4 5 6 7 8 9

Eine Schleife von 0 bis 12, bei der bei jedem Schleifendurchgang der Zähler um 3 erhöht wird, kann so geschrieben werden:

0.step(12, 3) {|x| print x, " " }
ergibt:
0 3 6 9 12

Gleichermaßen wird auch das Iterieren über Arrays und andere Container mit der Methode each leicht gemacht.

[ 1, 1, 2, 3, 5 ].each {|val| print val, " " }
ergibt:
1 1 2 3 5

Unterstützt eine Klasse einmal each, werden die zusätzlichen Methoden im Modul Enumerable (ab Seite 407 dokumentiert und auf den Seiten 104--105 zusammengefasst) verfügbar. Die Klasse File zum Beispiel stellt eine each-Methode bereit, die jede Zeile einer Datei retourniert. Wenn wir die Methode grep aus Enumerable benutzen, können wir nur über die Zeilen, die eine bestimmte Bedingung erfüllen, iterieren.

File.open("ordinal").grep /d$/ do |line|
  print line
end
ergibt:
second
third

Das Letzte hier (vielleicht sogar in beiden Bedeutungen) ist die einfachste Schleife von allen. Ruby unterstützt einen eingebauten Iterator namens loop.

loop {
  # block ...
}

Der loop-Iterator ruft den assoziierten Block unendlich Mal auf (oder zumindest, bis Sie die Schleife verlassen, aber Sie müssen weiterlesen, um herauszufinden, wie man das machen kann).

For ... In

Vorher haben wir gesagt, dass die einzigen eingebauten Schleifenprimitiva while und until wären. Was ist dann dieses ``for''-Ding? Nun, for ist ein Klumpen syntaktischer Zucker. Wenn Sie

for aSong in songList
  aSong.play
end

schreiben, übersetzt Ruby das in etwa in

songList.each do |aSong|
  aSong.play
end

Der einzige Unterschied zwischen der for-Schleife und der each-Form ist der Scope von lokalen Variablen, die im Körper der Schleife definiert werden. Das wird auf Seite 89 besprochen.

Sie können for benutzen, um über jedes Objekt zu iterieren, das der Methode each antwortet, wie zum Beispiel ein Array oder ein Range.

for i in ['fee', 'fi', 'fo', 'fum']
  print i, " "
end
for i in 1..3
  print i, " "
end
for i in File.open("ordinal").find_all { |l| l =~ /d$/}
  print i.chomp, " "
end
ergibt:
fee fi fo fum 1 2 3 second third

Solange Ihre Klasse eine vernünftige each-Methode definiert, können Sie eine for-Schleife benutzen, um sie zu durchlaufen.

class Periods
  def each
    yield "Classical"
    yield "Jazz"
    yield "Rock"
  end
end

periods = Periods.new for genre in periods   print genre, " " end
ergibt:
Classical Jazz Rock

Break, Redo und Next

Die Konstrukte zur Schleifenkontrolle break, redo und next erlauben es Ihnen, den normalen Fluss durch eine Schleife oder einen Iterator zu veränden.

break beendet die aktuelle Schleife; die Kontrolle wird bei der Anweisung, die dem Block folgt, wieder aufgenommen. redo wiederholt den aktuellen Schleifendurchlauf vom Beginn an, aber ohne die Bedingung neu auszuwerten oder das nächste Element zu holen (in einem Iterator). next springt zum Ende der Schleife und startet den nächsten Schleifendurchlauf.

while gets
  next if /^\s*#/   # skip comments
  break if /^END/   # stop at end
                    # substitute stuff in backticks and try again
  redo if gsub!(/`(.*?)`/) { eval($1) }
  # process line ...
end

Diese Schlüsselwörter können mit jedem Schleifenmechanismus, der auf Iteratoren beruht, verwendet werden:

i=0
loop do
  i += 1
  next if i < 3
  print i
  break if i > 4
end
ergibt:
345

Retry

Die Anweisung redo veranlasst eine Schleife, die aktuelle Iteration zu wiederholen. Manchmal aber müssen Sie die Schleife ganz an ihrem Anfang beginnen lassen. Die Anweisung retry ist ihre Eintrittskarte dafür. retry startet jede Art von Schleife neu.

for i in 1..100
  print "Now at #{i}. Restart? "
  retry if gets =~ /^y/i
end

Wenn Sie das interaktiv ausführen, könnte es so aussehen:

Now at 1. Restart? n
Now at 2. Restart? y
Now at 1. Restart? n
 . . .

retry liest jedes Argument des Iterators neu aus, bevor sie ihn neu startet. Die Onlinedokumentation zu Ruby zeigt folgendes Beispiel einer selbstgebastelten until-Schleife.

def doUntil(cond)
  yield
  retry unless cond
end

i = 0 doUntil(i > 3) {   print i, " "   i += 1 }
produces:
0 1 2 3 4

Variablenscope und Schleifen

Die Schleifen while, until und for sind in die Sprache eingebaut und führen keinen neuen Scope ein; bereits existente Variablen können in der Schleife benutzt werden und jede neue lokale Variable ist nachher verfügbar.

Die Blöcke die von Iteratoren (wie loop oder each) benutzt werden, sind etwas anders. Normalerweise kann auf die in diesen Blöcken erzeugen lokalen Variablen von außerhalb des Blocks nicht zugegriffen werden.

[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ]
ergibt:
prog.rb:4: undefined local variable or method `x'
for #<Object:0x4019ac90> (NameError)

Wenn jedoch zu dem Zeitpunkt, da der Block ausgeführt wird, eine lokale Variable mit dem selben Namen wie eine Variable, die im Block benutzt wird, existiert, wird diese im Block verwendet. Ihr Wert ist daher auch verfügbar, wenn der Block geendet hat. Wie das folgende Beispiel zeigt, trifft das sowohl auf normale Variablen im Block als auch auf die Parameter des Blocks zu.

x = nil
y = nil
[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ] » [3, 4]


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Übersetzung: Johannes Tanzler
Für das englische Original:
© 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0. That reference is available for download.
Diese Lizenz sowie das Original vom Herbst 2001 bilden die Grundlage der Übersetzung
Es wird darauf hingewiesen, dass sich die Lizenz des englischen Originals inzwischen geändert hat.
Für die deutsche Übersetzung:
© 2002 Jürgen Katins
Der Copyright-Eigner stellt folgende Lizenzen zur Verfügung:
Nicht-freie Lizenz:
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Freie Lizenz:
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".