Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Ruby.new



Als wir anfingen, dieses Buch zu schreiben, hatten wir den großen Plan (damals waren wir auch noch jünger). Wir wollten die Sprache von oben nach unten dokumentieren, angefangen mit Klassen und Objekten bis hin zu diesen kniffligen Syntax-Details. Das schien damals eine gute Idee zu sein. Schließlich ist in Ruby fast alles ein Objekt, also schien es sinnvoll, zuerst über Objekte zu reden.

So dachten wir jedenfalls.

Unglücklicherweise stellte sich heraus, dass es doch zu schwierig war, eine Sprache so zu beschreiben. Solange man keine Strings behandelt hat, if-Anweisungen und andere Einzelheiten, ist es schwierig, Beispiele für Klassen zu schreiben. Während unserer Von-oben-nach-unten-Beschreibung liefen wir dauernd auf diese grundlegenden Sprachdetails auf, die wir erst behandeln mussten, wenn unser Beispiel verständlich sein sollte.

Daher entwickelten wir einen anderen großen Plan (man nennt uns nicht umsonst pragmatisch). Wir beschreiben Ruby immer noch von oben nach unten. Aber vorher fügen wir ein kurzes Kapitel ein, das all die allgemeinen Sprachmerkmale und das spezielle Vokabular von Ruby beschreibt, eine Art Kurzeinführung, um auf den Rest des Buches vorzubereiten.

Ruby ist eine objekt-orientierte Sprache

Lasst es uns noch mal sagen. Ruby ist durch und durch eine objektorientierte Sprache. Alles was man manipuliert, ist ein Objekt und die Ergebnisse solcher Manipulationen sind wieder Objekte. Allerdings haben auch andere Sprachen diesen Anspruch. Sie haben dann aber eine unterschiedliche Auffassung davon, was objektorientiert bedeutet, und eine andere Terminologie für die benutzten Konzepte.

Bevor wir also zu tief in die Details gehen, wollen wir einen kurzen Blick auf die Begriffe und Schreibweisen werfen, die wir benutzen.

Wenn man objektorientierten Code schreibt, versucht man normalerweise, ein Modell der realen Welt in den Code zu übertragen. Üblicherweise entdeckt man während dieses Prozesses Kategorien von Dingen, die im Code repräsentiert sein müssen. In einer Jukebox könnte das Konzept eines ``Song'' eine soche Kategorie sein. In Ruby würde man eine Klasse definieren, um solche Einheiten zu repräsentieren. Eine Klasse ist eine Kombination aus Zuständen (zum Beispiel der Name des Songs) und Methoden, die auf diesen Zustand zugreifen (vielleicht eine Methode, um den Song abzuspielen).

Wenn man erstmal solche Klassen hat, wird man ein paar Instanzen davon erzeugen wollen. Für die Jukebox mit der Klasse Song wird man verschieden Instanzen von populären Hits brauchen, wie etwa ``Ruby Tuesday'', ``Enveloped in Python'', ``String of Pearls'', ``Small talk'' und so weiter. Das Wort Objekt wird auch statt Klasseninstanz benutzt (und weil wir schreibfaul sind, werden wir es wahrscheinlich oft benutzen).

In Ruby werden diese Objekte erzeugt, indem man einen Konstruktor aufruft, eine spezielle mit der Klasse verbundene Methode. Der Standardkonstruktor heißt new.

song1 = Song.new("Ruby Tuesday")
song2 = Song.new("Enveloped in Python")
# and so on

Diese Instanzen wurden beide aus der selbern Klasse erzeugt, aber sie haben unterschiedliche Eigenschaften. Zum einen hat jedes Objekt einen eindeutigen Objektbezeichner (abgekürzt als Objekt Id). Zum Anderen kann man Instanz-Variablen definieren, Variablen mit für jede Instanz unterschiedlichen Werten. Diese Instanz-Variablen beinhalten den Zustand des jeweiligen Objekts. So hat etwa jeder unserer Songs eine Instanz-Variable, die seinen Song-Titel enthält.

Innerhalb einer jeden Klasse kann man Instanz-Methoden definieren. Jede Methode ist ein Stückchen Code Funktionalität, die man von innerhalb der Klasse aufrufen kann und (abhängig von gewissen Zugriffsregeln) auch von außerhalb. Diese Instanz_Methoden haben wiederum Zugriff auf die Instanz-Variablen dieses Objektes, und somit auf seinen Zustand.

Methoden werden aufgerufen, indem man dem Objekt eine Meldung schickt. Diese Meldung enthält den Namen der Methode, zusammen mit den Parametern, die die Methode so braucht. [Diese Idee der Methodenaufrufe durch Meldungen kommt von Smalltalk.] Wenn ein Objekt eine Meldung erhält, sucht es in seiner eigenen Klasse nach der entsprechenden Methode. Wird sie gefunden, so wird die Methode ausgeführt. Wird sie nicht gefunden, ... nun ja, das erklären wir dann später.

Dieses Geschäft mit Methoden und Meldungen mag vielleicht etwas kompliziert erscheinen, in der Praxis ist es aber sehr eingänglich. Wie sehen uns ein paar solcher Methoden-Aufrufe an. (Nicht vergessen: die Pfeile in den Code-Beispielen zeigen auf den Wert, der von den Ausdrücken erzeugt wird.)

"gin joint".length » 9
"Rick".index("c") » 2
-1942.abs » 1942
sam.play(aSong) » "duh dum, da dum de dum ..."

Hier wird das Ding vor dem Punkt Empfänger genannt und der Name danach ist die aufzurufende Methode. Im ersten Beispiel wird ein String (eine Zeichenkette) nach seiner Länge gefragt, im zweiten wird ein anderer String gefragt, wo in ihm der Buchstabe ``c'' vorkommt. In der dritten Zeile soll eine Zahl ihren Absolutbetrag zurückgeben und zum Schluss bitten wir Sam, uns ein Lied zu spielen.

Hier sollten wir nun einen bedeutenden Unterschied zwischen Ruby und den meisten anderen Sprachen ansprechen. In (sagen wir mal) Java findet man den Absolutwert einer Zahl, indem man eine separate Funktion aufruft und ihr diese Zahl mitgibt. Man schreibt dann etwa

number = Math.abs(number)     // Java code

In Ruby ist die Fähigkeit, Absolutwerte zu bestimmen, in die Zahlen selbst eingebaut -- sie kümmern sich selber um interne Angelegenheiten. Man schickt einfach die Meldung abs zu einem Zahl-Objekt und lässt dieses dann die Arbeit machen.

number = number.abs

Das selbe gilt für alle Ruby-Objekte: In C würde man schreiben strlen(name), in Ruby dagegen name.length und so weiter. Teilweise ist es dies, wenn wir sagen, dass Ruby von Grund auf eine OO-Sprache ist.

Einige Ruby Grundlagen

Nur die wenigsten Leute mögen einen Haufen von langweiligen Syntax-Regeln lesen, wenn sie eine neue Sprache lernen. Also mogeln wir ein bisschen. In diesem Abschnitt werden wir einige Highlights ansprechen, den Kram den man eben einfach wissen muss, wenn man Ruby-Programme schreiben will. Später in Kapitel 18, das auf Seite 201 beginnt, gehen wir dann ans Eingemachte.

Fangen wir mit einem einfachen Ruby-Programm an. Wir werden eine Methode schreiben, die einen String mit einem hinzugefügten Namenzurückgibt. Wir werden diese Methode mehrere Male aufrufen.

def sayGoodnight(name)
  result = "Goodnight, " + name
  return result
end

# Time for bed... puts sayGoodnight("John-Boy") puts sayGoodnight("Mary-Ellen")

Zunächst einige allgemeine Beobachtungen. Die Syntax von Ruby ist sauber. Man braucht keine Semikolons am Ende jeder Anweisung, solange man jede Anweisung in eine Zeile packt. Kommentare in Ruby fangen mit einem # an und gehen bis ans Ende der Zeile. Das Layout des Codes ist Sache des Autors, Einrückungen sind nicht signifikant.

Methoden werden mit dem Schlüsselwort def definiert, dann kommt der Methoden-Name (in diesem Fall ``sayGoodnight'') und dann die Parameter der Methode in Klammern. Ruby benutzt keine geschweiften Klammern, um die Anweisungen und Definitionen zu begrenzen. Stattdessen beendet man einfach den Methodenrumpf mit dem abschließenden Schlüsselwort end. Unser Methodenrumpf ist ziemlich einfach. In der ersten Zeile hängt man den Parameter name an den literalen String ``Goodnight,[visible space]'' dran und besetzt damit die lokale Variable result. In der nächsten Zeile wird dieses result an den Aufrufer zurückgegeben. Beachte dass wir die Variable result nicht haben deklarieren müssen, sie wurde geboren, als wir auf sie zugriffen.

Nachdem wir die Methode definiert haben, rufen wir sie zweimal auf. In beiden Fällen geben wir das Ergebnis weiter an die Methode puts, die einfach ihr Argument gefolgt von einem Newline ausgibt.

Goodnight, John-Boy
Goodnight, Mary-Ellen

Die Zeile ``puts sayGoodnight("John-Boy")'' enthält zwei Methodenaufrufe, einen für sayGoodnight und einen für puts. Warum sind jetzt bei dem einen Aufruf die Argumente in Klammern und bei dem andern nicht? In diesem Fall ist das einfach nur Geschmacksache. die folgenden Zeilen sind alle äquivalent.

puts sayGoodnight "John-Boy"
puts sayGoodnight("John-Boy")
puts(sayGoodnight "John-Boy")
puts(sayGoodnight("John-Boy"))

Dummerweise ist das Leben nicht immer so einfach, und Vorrangregeln können es ganz schön schwer machen zu entscheiden, welches Argument zu welcher Methode gehört. Wir empfehlen also: immer schön Klammern benutzen, außer in wirklich ganz einfachen Fällen.

Diese Beispiel zeigt auch einige String-Objekte von Ruby. Es gibt viele Wege, ein String-Objekt zu erzeugen, aber der wahrscheinlich einfachste Weg geht über literale Strings: Folgen von Buchstaben in einfachen oder doppelten Hochkommas. Der Unterschied zwischen beiden liegt in dem Ausmaß an Arbeit, die Ruby beim Erzeugen des Literals leistet. Bei einfachen Hochkommas macht Ruby nicht viel. Mit wenig Ausnahmen ist das, was man in den String hineintippt, das, was der Wert des Strings wird.

Im Fall von doppelten Hochkommas leistet Ruby mehr. Erstmal schaut Ruby nach Ersetzungen -- Buchstabenfolgen, die mit einem Backslash anfangen -- und ersetzt diese durch den entsprechenden Binärwert. Der gebräuchlichste ist ``\n'' zum Erzeugen einer neuen Zeile.

puts "And Goodnight,\nGrandma"
produces:
And Goodnight,
Grandma

Als zweites ersetzt Ruby Ausdrücke. Innerhalb des Strings wird #{expression} ersetzt durch den Wert von expression. Wir können das benutzen, um unsere frühere Methode anders zu schreiben.

def sayGoodnight(name)
  result = "Goodnight, #{name}"
  return result
end

Wenn Ruby den String erzeugt, sucht es sich den derzeitigen Wert von name raus und setzt das in den String ein. Die Ausdrücke im #{...}-Konstrukt dürfen so komplex sein wie nur denkbar sein. Man braucht die geschweiften Klammern nicht unbedingt, wenn der Ausdruck eine globale, Instanz- oder Klassen-Variable ist. Mehr Informationen über Strings und andere Standard-Typen gibts in Kapitel 5 ab Seite 49.

Zum Schluss können wir diese Methode noch weiter vereinfachen. Der Wert, den eine Methode zurückgibt, ist der Wert des letzten berechneten Ausdrucks, also können wir das return auch noch weglassen.

def sayGoodnight(name)
  "Goodnight, #{name}"
end

Wir haben versprochen, dass dieser Abschnitt kurz werden würde. Aber einen Punkt müssen wir noch besprechen: Namen in Ruby. Ums kurz zu machen benutzen wir ein paar Ausdrücke (wie Klassen-Variablen), die wir noch nicht definiert haben. Trotzdem, wenn wir diese Regeln jetzt besprechen, hast man schon einen Vorsprung, wenn wir später zu den Instanz-Variablen und diesem Kram kommen.

Ruby benutzt eine Übereinkunft, um besser zwischen den Namenstypen zu unterscheiden: der erste Buchstabe sagt, wozu der Name benutzt wird. Lokale Variablen, Methoden-Parameter und Methoden-Namen sollten alle mit einem kleinen Buchstaben anfangen oder einem Underscore. Globale Variablen fangen mit einem Dollarzeichen ($) an und Instanz-Variablen fangen mit einem ``at''-Zeichen (@) an. Klassen-Variablen fangen mit zwei ``at''-Zeichen an (@@). Schließlich sollten Klassen-Namen, Modul-Namen und Konstanten mit einem Großbuchstaben anfangen. Beispiele von unterschiedlichen Namen gibts in Tabelle 2.1 auf Seite 10.

Nach diesem ersten Zeichen kann jedwede Kombination aus Buchstaben, Ziffern und Underscores fogen (nur nach @ soll keine Ziffer kommen).

Beispiel-Variablen und -Klassen-Namen
Variablen Konstanten und
Lokale Globale Instanz Klassen Klassen-Namen
name $debug @name @@total PI
fishAndChips $CUSTOMER @point_1 @@symtab FeetPerMile
x_axis $_ @X @@N String
thx1138 $plan9 @_ @@x_pos MyClass
_26 $Global @plan9 @@SINGLE Jazz_Song

Arrays und Hashes

Rubys Arrays und Hashes sind indizierte Collections. Beide speichern eine Reihe von Objekten, auf die über einen Schlüssel zugegriffen werden kann. Bei Arrays ist der Schlüssel ein Integer (Ganzzahl), bei Hashes kann das jede Form von Objekt sein. Sowohl Arrays als auch Hashes wachsen mit, wenn neue Elemente dazukommen. Der Zugriff auf Arrays ist effizienter, der auf Hashes flexibler. Jedes einzelne Array oder Hash kann Objekte unterschiedlichen Typs beinhalten; man kann in ein Array ein Integer, einen String und eine Fließkommazahl hineinpacken, wie wir gleich sehen werden.

Man kann ein Array erzeugen und initialisieren durch ein literales Array -- ein Satz von Elementen zwischen eckigen Klammern. Bei einem gegebenen Array-Objekt kann man auf einzelne Elemente durch Angabe eines Index in eckigen Klammern zugreifen. Das sieht so aus.

a = [ 1, 'cat', 3.14 ]   # Array mit drei Elementen
# Zugriff auf  erstes Element
a[0] » 1
# das dritte Element setzen
a[2] = nil
# das komplette Array
a » [1, "cat", nil]

Man kann ein leeres Array erzeugen, indem man ein literales Array mit keinen Elementen benutzt oder mit dem Array-Objekt-Konstuktor, Array.new.

empty1 = []
empty2 = Array.new

Manchmal ist es eine Qual, ein Array mit Wörtern zu erzeugen, mit all den Hochkommas und Kommas. Glücklicherweise gibts da eine Abkürzung: %w macht da genau das, was wir brauchen

a = %w{ ant bee cat dog elk }
a[0] » "ant"
a[3] » "dog"

Hashes in Ruby sind ähnlich wie Arrays. Für ein Hash-Literal nimmt man geschweifte Klammern statt eckigen. Das Literal muss zwei Objekte für jeden Eintrag besitzen: einen für den Schlüssel, den anderen für den Wert.

Als Beispiel wollen wir Musikinstrumente zu ihrer Abteilung im Orchester zuordnen. das kann man mit einem Hash machen.

instSection = {
  'cello'     => 'string',
  'clarinet'  => 'woodwind',
  'drum'      => 'percussion',
  'oboe'      => 'woodwind',
  'trumpet'   => 'brass',
  'violin'    => 'string'
}

Hashes werden mit denselben eckigen Klammern indiziert wie Arrays.

instSection['oboe'] » "woodwind"
instSection['cello'] » "string"
instSection['bassoon'] » nil

Wie das letzte Beispiel zeigt, gibt ein Hash dafaultmäßig nil zurück, wenn man über einen Schlüssel zugreift, der nicht existiert. Normalerweise reicht das auch, weil in Abfrageanweisungen nil dasselbe wie false bedeutet. Man kann diesen Defaultwert aber auch ändern. Wenn man etwa einen Hash benutzt, um zu zählen, wie oft ein Schlüssel vorkommt, so ist es sinnvoll, diesen Defaultwert auf Null zu setzen. Dies macht man einfach, indem man beim Erzeugen eines neuen, leeren Hashs diesen Defaultwert mit angibt

histogram = Hash.new(0)
histogram['key1'] » 0
histogram['key1'] = histogram['key1'] + 1
histogram['key1'] » 1

Array- und Hash-Objekte besitzen eine Menge nützlicher Methoden: siehe die Erläuterungen ab Seite 35, die Referenz ab 282 und 321.

Kontroll-Strukturen

Ruby besitzt all die üblichen Kontroll-Strukturen, wie if-Abfragen und while-Schleifen. Java-, C- und Perl-Programmierer werden überrascht sein, dass die üblichen Klammern um den Rumpf dieser Anweisungen fehlt. Statdessen benutzt Ruby das Schlüsselwort end, um das Ende einer solchen Anweisung anzuzeigen.

if count > 10
  puts "Try again"
elsif tries == 3
  puts "You lose"
else
  puts "Enter a number"
end

Genauso werden while-Anweisungen mit end abgeschlossen.

while weight < 100 and numPallets <= 30
  pallet = nextPallet()
  weight += pallet.weight
  numPallets += 1
end

Statement Modifiers in Ruby sind nützliche Abkürzungen, wenn der Rumpf einer if- oder while-Anweisung nur aus einem Ausdruck besteht. Man schreibt einfach diesen Ausdruck, gefolgt von if oder while und der Bedingung. Als Beispiel hier eine einfache if-Anweisung.

if radiation > 3000
  puts "Danger, Will Robinson"
end

Hier dasselbe, jetzt mit Statement Modifier.

puts "Danger, Will Robinson" if radiation > 3000

das Gleiche als while-Schleife

while square < 1000
   square = square*square
end

daraus wird etwas kürzer

square = square*square  while square < 1000

Diese Statement Modifiers sollten Perl-Programmierern bekannt vorkommen.

Reguläre Ausdrücke

Die meisten Standard-Typen von Ruby werden allen Programmierern geläufig sein. Die meisten Sprachen kennen Strings, Integers, Fließkommazahlen, Arrays und so weiter. Allerdings gab es bis Ruby eine Unterstützung für reguläre Ausdrücke nur in den sogenannten Script-Sprachen wie Perl, Python und awk. Das ist schade: reguläre Ausdrücke sind, wenn auch knifflig, ein sehr machtvolles Werkzeug beim Umgang mit Text.

Ganze Bücher sind schon über reguläre Ausdrücke geschrieben worden (wie etwa Mastering Regular Expressions ), also werden wir gar nicht erst versuchen, das alles in einem kurzen Abschnitt zu behandeln. Stattdessen schauen wir ein paar Beispielen für reguläre Ausdrücke bei der Arbeit zu. Die ganze Abhandlung über reguläre Ausdrücke fängt auf Seite 58 an.

Mit einem regulären Ausdruck spezifiziert man ein Muster aus Zeichen, das in einem String gesucht werden soll. In Ruby erzeugt man üblicherweise einen regulären Ausdruck, indem man ein Muster zwischen Slashes schreibt. Und, das ist Ruby, reguläre Ausdrücke sind natürlich wieder Objekte und können als solche benutzt werden.

Als Beispiel schreiben wir ein Muster, das auf einen String passt, der den Text ``Perl'' oder den Text``Python'' enthält. Der reguläre Ausdruck sieht dann so aus.

/Perl|Python/

Die Schrägstriche begrenzen das Muster, das aus zwei Teilen besteht, getrennt durch ein Pipe-Zeichen (``|''). Man kann Klammern innerhalb des Musters benutzen, gerade wie in einem arythmetischen Ausdruck. Dieses Muster könnte also auch so aussehen.

/P(erl|ython)/

Man kann auch Wiederholungen innerhalb eines Musters angeben. /ab+c/ passt auf einen String mit einem ``a'' gefolgt von einem oder mehreren ``b''s, gefolgt von einem ``c''. Wenn man das Plus durch ein Sternchen ersetzt, so passt /ab*c/ auf ein ``a'', keinem, einem oder mehreren ``b''s und einem ``c''.

Man kann auch ein Zeichen aus einer Gruppe von Zeichen suchen. Die üblichen Beispiele sind Zeichen-Klassen wie ``\s'', was auf ein Whitespace-Zeichen passt (Leertaste, Tabulator, Newline und so was). ``\d'' passt auf Ziffern, ``\w'' passt auf einen üblichen Buchstaben und ``.'' passt auf ein einzelnes Zeichen.

Mit all dem zusammen kann man einige nützliche reguläre Ausdrücke erzeugen.

/\d\d:\d\d:\d\d/     # eine Zeit so wie 12:34:56
/Perl.*Python/       # Perl, kein, ein oder mehrere andere Zeichen, dann Python
/Perl\s+Python/      # Perl, ein oder mehrere Whitespaces, dann Python
/Ruby (Perl|Python)/ # Ruby, ein Leerzeichen und entweder Perl oder Python

Wenn man nun ein Muster erzeugt hat, sollte man es auch nutzen. Der Match-Operator ("sucht nach") ``=~'' wird benutzt, um einen String mit einem regulären Ausdruck zu vergleichen. Wenn das Muster in dem String gefunden wird, so gibt =~ die Startposition zurück, wenn nicht ein nil. Damit kann man reguläre Ausdrücke in if- und while-Anweisungen benutzen. Als Beispiel schreibt das folgende Stück Code eine Meldung, wenn ein String den Text 'Perl' oder 'Python' enthält.

if line =~ /Perl|Python/
  puts "Scripting language mentioned: #{line}"
end

Man kann den Teil eines Strings, der auf einen regulären Ausdruck passt, auch durch einen anderen Text ersetzen, wenn man eine der Ersetzungs-Methoden aus Ruby benutzt.

line.sub(/Perl/, 'Ruby')    # ersetzt das erste 'Perl' durch 'Ruby'
line.gsub(/Python/, 'Ruby') # ersetzt alle 'Python' durch 'Ruby'

Im Rest des Buches werden wir noch viel mehr über reguläre Ausdrücke zu sagen haben.

Blöcke und Iteratoren

Dieser Abschnitt beschreibt kurz eine von Rubys besonderen Stärken. Wir schauen uns jezt Code-Blöcke an: Code-Stücke, die man mit Methoden-Aufrufen verbinden kann, grad so als wärens Parameter. Das ist eine unbeschreiblich mächtige Sache. Man kann Code-Blöcke benutzen, um Callbacks zu implementieren (die sind aber einfacher als die anonymen inneren Klassen von Java), um Code-Stücke durch die Gegend zu schicken (das geht aber flexibler als mit Fnktions-Pointern in C) und um Iteratoren zu implementieren.

Code-Blöcke sind einfach nur Stücke von Code, die in geschreiften Klammern stehen oder in do...end.

{ puts "Hello" }       # das ist ein Block

do                     #   club.enroll(person)  # und das hier auch   person.socialize     # end                    #

Wenn man jetzt so einen Block hat, kann man ihn mit einem Aufruf an eine Methode binden. Diese Methode kann dann den Block einmal oder mehrmals aufrufen, wobei sie die yield-Anweisung von Ruby benutzt. Das folgende Beispiel zeigt dies. Wir definieren eine Methode, die yield zweimal benutzt. Dann rufen wir diese Methode auf, wobei wir den erwünschten Block auf die selbe Zeile dahinter setzen (und hinter eventuell vorhandene Parameter) (der Übersetzer: zumindest muss der Block in der selben Zeile anfangen). [Manche Leute stellen sich diese Verbindung eines Blockes mit einer Methode gern als eine Art von Parameterübergabe vor. Das kann man machen, es trifft die Sache aber nicht ganz genau. Man sollte sich den Block und die Methode lieber als zwei Ko-Routinen vorstellen, die die Programmkontrolle zwischen sich hin und her schieben.]

def callBlock
  yield
  yield
end

callBlock { puts "In the block" }
erzeugt:
In the block
In the block

Beachte, wie der Code in dem Block (puts "In the block") zweimal ausgeführt wird, einmal für jedes yield.

Man kann dem yield auch Parameter mitgeben, diese werden an den Block weitergegeben. Innerhalb des Blocks definiert man dann in senkrechten Strichen (``|'') eine Liste mit Namen, um diese Parameter entgegenzunehmen.

  def callBlock
    yield , 
  end

callBlock { |, | ... }

(der Übersetzer: was macht denn das Komma da, das ist doch kein Name und überhaupt ist das schlecht erklärt.) Code-Blöcke werden in der Ruby-Bibliothek benutzt, um Iteratoren zu implementieren: Methoden, die nacheinander alle Elemente jeglicher Art von Liste, wie etwa ein Array, zurückgeben.

a = %w( ant bee cat dog elk )    # erzeuge ein Array
a.each { |animal| puts animal }  # über dessen Inhalt iterieren
erzeugt:
ant
bee
cat
dog
elk

Nun schauen wir uns an, wie wir den each-Iterator der Array-Klasse aus dem vorigen Beispiel benutzen können. Der each-Iterator springt durch jedes Element aus dem Array und ruft dabei jedes Mal each mit diesem Element auf. In Pseudo-Code sieht das etwa so aus:

# Innerhalb der Array-Klasse...
def each
  for each element
    yield(element)
  end
end

Deshalb kann man über die Elemente eines Arrays mit der each-Methode iterieren und dabei einen Block mitschicken. Dieser Block wird dann für jedes Element aufgerufen.

[ 'cat', 'dog', 'horse' ].each do |animal|
  print animal, " -- "
end
erjeugt:
cat -- dog -- horse --

Auf die selbe Art werden aus vielen Schleifen-Konstrukten, die es in Sprachen wie C oder Java gibt, einfache Methoden-Aufrufe in Ruby, wobei die Methoden den gewünschten Block von null-mal bis mehrmals aufrufen.

5.times {  print "*" }
3.upto(6) {|i|  print i }
('a'..'e').each {|char| print char }
erzeugt:
*****3456abcde

Hier bitten wir die Zahl 5, einen Block fünfmal aufzurufen, dann bitten wir die Zahl 3, einen Block solange mit ansteigenden Werten aufzurufen, vbis die 6 erreicht ist. Am Schluss ruft die Folge der Buchstaben von ``a'' bis ``e'' mit der each-Methode einen Block auf.

Lesen und Sreiben

Ruby besitzt eine verständliche I/O-Bibliothek. Dennoch werden wir in den meisten Beispielen in diesem Buch auf nur wenige einfache Methoden zurückgreifen. Wir haben schon zwei Methoden kennengelernt, die für Output zuständig sind. puts schreibt jedes seiner Elemente mit jeweils einem Newline (neue Zeile). print macht das auch, aber ohne Newline. Beide können auf alle möglichen Ausgabe-Objekte schreiben, aber im Normalfall schreiben sie auf die Konsole.

Eine andere Methode, die wir oft benutzen werden, ist printf, die ihre Argumente ausgibt mithilfe eines Format-Strings (gerade wie printf in C oder Perl).

printf "Number: %5.2f, String: %s", 1.23, "hello"
erzeugt:
Number:  1.23, String: hello

in diesem Beispiel erzählt der Format-String "Number: %5.2f, String: %s" der printf-Methode, dass sie eine Umwandlung vornehmen soll in eine Fleißkommazahl (mit 5 moglichen Zeichen, 2 nach dem Dezimalpunkt) und in einen String.

Es gibt viele Wege, Input in dein Programm zu kriegen. Die wahrscheinlich gebräuchlichste Methode ist die Routine gets, die die nächste Zeile aus dem Standard-Input-Kanal (meistens die Tastatur) des Programms holt.

line = gets
print line

Die gets-Routine hat einen Seiteneffekt: sie gibt nicht nur die gerade gelesene Zeile zurück, sie speichert sie auch in der globalen Variablen $_. Diese Variable ist insofern speziell, als dass sie in vielen Fällen als Default-Variable genutzt wird. Wenn man print ohne Argument aufruft, gibt das den Inhalt von $_ aus. Wenn man eine if- oder while-Anweisung nur mit einem regulären Ausdruck als Bedingung schreibt, wird dieser Ausdruck gegen $_ getestet. Während einige Puristen dies als frühsteinzeitliche Faustkeilprogrammierung ansehen, kann diese Abkürzung einem helfen, recht knappe Programme zu schreiben. Als Beispiel gibt das folgende Programm alle Zeilen des Input-Kanals aus, die das Wort ``Ruby'' enthalten.

while gets           # Zeile auf $_ setzen
  if /Ruby/          # prüft gegen $_
    print            # Ausgabe $_
  end
end

In richtiger ``Ruby-Schreibweeise'' würde man natürlich einen Iterator verwenden.

ARGF.each { |line|  print line  if line =~ /Ruby/ }

Dabei wird das vordefinierte Objekt ARGF benutzt, das den vom Programm benutzten Input-Kanal repräsentiert.

Vorschau und Ausblick

Das wars. Hiermit beenden wir unsere blitzschnelle Einführungstour über einige grundlegende Eigenschaften von Ruby. Wir haben einen kurzen Überblick gegeben über Objekte, Methoden, Strings, Container und reguläre Ausdrücke, wir haben uns einige ziemlich haarige Iteratoren angesehen. Hoffentlich hat dieses Kapitel dir genug Material geliefert, dass du den Rest des Buches durchstehen kannst.

Es ist Zeit, vorwärts zu schauen, und aufwärts --- hinauf zu einer höheren Warte. Als nächstes schauen wir uns Klassen und Objekte an, Sachen, die die Konstrukte der obersten Ebene von Ruby und gleichzeitig den grundlegenden Untergrund der gesamten Sprache bilden.


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Übersetzung: Jürgen Katins
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".