Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Die Sprache Ruby



Dieses Kapitel liefert eine Ansicht Rubys von unten nach oben. Anders als die bisherige Anleitung werden wir uns hier allein auf die Darstellung der Fakten beschränken anstatt die Design-Eigenschaften der Sprache zu besprechen. Wir werden außerdem die eingebauten Klassen und Module nach Möglichkeit ignorieren. Diese werden detailiert ab Seite 279 beschrieben.

Wenn der Inhalt dieses Kapitels einem bekannt vorkommt, so ist das durchaus richtig so; wir haben all dieses schon in den vorausgegangenen Kapiteln der Anleitung besprochen. Man sollte dieses Kapitel als in sich geschlossene Referenz der Grundlagen von Ruby betrachten.

Layout der Quellen

Ruby-Programme werden in 7-Bit ASCII geschrieben. [Ruby unterstützt außerdem umfangreich das Kanji, wobei die EUC, SJIS oder UTF-8 Codierung benutzt wird. Wenn eine andere Codierung als 7-Bit ASCII benutzt wird, muss die KCODE-Option benutzt werden, wie auf Seite 139 beschrieben.]

Ruby ist eine zeilenorientierte Sprache. Ruby-Ausdrücke und -Anweisungen werden durch das Zeilenende begrenzt, außer die Anweisung ist offensichtlich unvollständig --- wenn zum Beispiel das letzte Teil auf einer Zeile ein Operator oder ein Komma ist. Man kann Semikolons benutzen, um mehrere Ausdrücke auf eine Zeile zu bekommen. Weiter kann man mit einen Backslash (`\') anzeigen, dass der Ausdruck auf der nächsten Zeile weitergehen soll. Kommentare fangen mit einem `#' an und gehen bis zum Zeilenende. Kommentare werden während der Kompilierung ignoriert.

a = 1

b = 2; c = 3

d = 4 + 5 +      # braucht kein '\'     6 + 7

e = 8 + 9   \     + 10         # '\' benötigt

Physikalische (echte) Zeilen, die zwischen einer Zeile mit =begin am Anfang und einer Zeile mit =end am Anfang liegen, werden vom Compiler ignoriert und können für eingebettete Dokumentationen dienen (siehe Anhang A ab Seite 517).

Ruby liest seinen Input in einem Durchgang (Single Pass), damit kann man Programme durch eine Pipe an den Standardeingang stdin des Compilers schicken.

echo 'print "Hello\n"' | ruby

Wenn der Compiler irgendwo im Programm auf eine Zeile stößt, die nur ``__END__'' enthält ohne Whitespaces vorne oder hinten dran, dann wird diese Zeile als Ende des Programms angesehen --- alle folgenden Zeilen werden nicht mehr compiliert. Allerdings können diese Zeilen in das laufende Programm eingelesen werden, wenn man das globale IO-Objekt DATA benutzt, wie auf Seite 219 beschrieben.

BEGIN und END Blöcke

In jeder Ruby-Quelldatei kann man Code-Blöcke deklarieren, die laufen sollen, wenn die Datei geladen wird (die BEGIN-Blöcke) und nachdem das Programm abgeschlossen ist (die END-Blöcke).

BEGIN {
  begin code
}

END { end code }

Ein Programm darf mehrere BEGIN- und END-Blöcke besitzen. Die BEGIN-Blöcke werden in der Reihenfolge ihres Auftretens abgearbeitet, die END-Blöcke in der umgedrehten Reihenfolge.

Allgemein begrenzter Input

Es gibt alternative Formen für literale Strings, Arrays, reguläre Ausdrücke und Shell-Kommandos, die mit einer algemein begrenzenden Syntax arbeiten. All diese Literale fangen mit einem Prozent-Zeichen an, und dann kommt ein einzelner Buchstabe, der den Typ des Literals bezeichnet. Diese Buchstaben werden zusammengefasst in Tabelle 18.1 auf Seite 203; die dazugehörenden Literale werden in dem entsprechenden Abschnitt weiter unten in diesem Kapitel behandelt.

Allgemein begrenzter Input
Typ Bedeutung Siehe Seite
%q String mit einfachen Anführungszeichen 204
%Q, % String mit doppelten Anführungszeichen 204
%w Array aus Tokens 206
%r Muster eines regulären Ausdrucks 207
%x Shell-Kommando 220

Auf den Buchstaben für den Typ folgt der Begrenzer, der ein beliebiges Zeichen sein kann. Wenn der Begrenzer eines dieser Zeichen ist ``('', ``['', ``{'' oder ``<'', dann geht das Literal bis zu dem passenden schließenden Begrenzer, wobei ins Literal eingebaute Klammernpaare mitgezählt werden. Bei allen anderen Begrenzern endet das Literal beim nächsten Auftreten des Begrenzers.

%q/this is a string/
%q-string-
%q(a (nested) string)

So begrenzte Strings dürfen über mehrere Zeilen gehen.

%q{def fred(a)
     a.each { |i| puts i }
   end}

Die Basis-Typen

Die Basis-Typen in Ruby sind Zahlen, Strings, Arrays, Hashes, Ranges, Symbole und reguläre Ausdrücke.

Integer und Fließkomma-Zahlen

Ruby-Integer sind Objekte der Klasse Fixnum oder Bignum. Fixnum-Objekte enthalten Integers, die so groß sind wie ein Word des Rechners minus 1 Bit. Immer wenn ein Fixnum-Objekt diesen Bereich überschreitet, wird es automatisch in ein Bignum-Objekt konvertiert, dessen Größe nur durch den verfügbaren Speicherplatz begrenzt ist. Wenn eine Operation auf ein Bignum ein Ergebnis liefert, das in ein Fixnum reinpasst, dann wird dieses Ergebnis wieder in ein Fixnum gesteckt.

Integers schreibt man mit einem optionalen Vorzeichen, einem optionalen Indikator für die Basis (0 (das sind Nullen) für oktal, 0x für hexadezimal oder 0b für binär), gefolgt von einem String mit den zu dieser Basis passenden Ziffern. Underscores werden in diesem String ignoriert.

Man kann den Integer-Wert eines ASCII-Zeichens erhalten, indem man ein Fragezeichen vor das Zeichen setzt. Control- und Meta-Kombinationen (auf deutschen Tastaturen sind das Strg- und Alt-Tasten-Kombinationen) von Buchstaben können gleichfalls mit ?\C-x, ?\M-x, and ?\M-\C-x erzeugt werden. Man erhält die Control-Version eines Zeichens ch mit ch & 0x9f und die Meta-Version mit ch | 0x80. Den Integer-Wert des Backslash bekommt man mit ?\\.

123456                    # Fixnum
123_456                   # Fixnum (Underscore wird ignoriert)
-543                      # Negative Fixnum
123_456_789_123_345_789   # Bignum
0xaabb                    # Hexadezimal
0377                      # Oktal
-0b1010                   # Binär (negativ)
0b001_001                 # Binär
?a                        # Zeichen-Code
?A                        # Zeichen-Code des Großbuchstabens
?\C-a                     # Control-a = A - 0x40
?\C-A                     # Großschreibung bei Control wird ignoriert
?\M-a                     # Meta setzt Bit 7
?\M-\C-a                  # Meta- und Control-a

Eine literale Zahl mit einem Dezimalpunkt und/oder einem Exponenten wird in ein Float-Objekt umgewandelt, entsprechend dem double-Datentyp des Rechners. Auf den Dezimalpunkt muss eine Ziffer folgen, sonst versucht 1.e3 die Methode e3 der Klasse Fixnum aufzurufen.

12.34 » 12.34
-.1234e2 » -12.34
1234e-2 » 12.34

Strings

Ruby unterstützt eine ganze Reihe von Mechanismen, um literale Strings zu erzeugen. Jeder erzeugt ein Objekt vom Typ String. Die verschiedenen Mechanismen unterscheiden sich darin, wie der String abgegrenzt wird und wie viel Ersetzungen in ihm vorgenommen werden.

String-Literale in einfachen Anführungszeichen ('stuff' und %q/stuff/) erleben am wenigsten Ersetzungen. Beide konvertieren die Folge \\ in einen einfachen Backslash. Die Form mit den einfachen Anführungszeichen konvertiert noch \' in ein einfaches Anführungszeichen.

'hello' » hello
'a backslash \'\\\'' » a backslash '\'
%q/simple string/ » simple string
%q(nesting (really) works) » nesting (really) works
%q no_blanks_here ; » no_blanks_here

Strings in doppelten Anführungszeichen ("stuff", %Q/stuff/ und %/stuff/) erleben zusätzliche Ersetzungen, zu sehen in Tabelle 18.2 auf Seite 205.

Ersetzungen bei Strings in doppelten Anführungszeichen

\a Bell/alert (0x07) \nnn Oktal nnn
\b Backspace (0x08) \xnn Hex nn
\e Escape (0x1b) \cx Control-x
\f Formfeed (0x0c) \C-x Control-x
\n Newline (0x0a) \M-x Meta-x
\r Return (0x0d) \M-\C-x Meta-control-x
\s Space (0x20) \x x
\t Tab (0x09) #{expr} Wert von expr
\v Vertical tab (0x0b)

a  = 123
"\123mile" » Smile
"Say \"Hello\"" » Say "Hello"
%Q!"I said 'nuts'," I said! » "I said 'nuts'," I said
%Q{Try #{a + 1}, not #{a - 1}} » Try 124, not 122
%<Try #{a + 1}, not #{a - 1}> » Try 124, not 122
"Try #{a + 1}, not #{a - 1}" » Try 124, not 122

Strings dürfen über mehrere Input-Zeilen laufen, dann sind da aber Newline-Zeichen drin. Man kann lange String-Literale auch mit here-Dokumenten ausdrücken. Wenn Ruby auf die Folge <<identifier oder <<quoted string trifft, ersetzt es das mit dem aus dem folgenden logischen Input hergestellten Zeilen. Das hört erst dann auf, wenn es wieder auf eine Zeile trifft mit dem identifier oder dem quoted string am Anfang. Man kann ein Minuszeichen dierekt nach den << setzen, dann darf man den Ende-String auch einrücken. Wenn man einen String in einfachen Anführungszeichen als Ende-String nimmt, dann gelten die Ersetzungsregeln für einfache Anführungszeichen, sonst die für doppelte.

a = 123
print <<HIER
Regeln für doppelte \
Anführungszeichen gelten.
Sum = #{a + 1}
HIER

print <<-'DORT'     Einfache Anführungszeichen.     Oben stand auch #{a + 1}     DORT
erzeugt:
Regeln für doppelte Anführungszeichen gelten.
Sum = 124
    Einfache Anführungszeichen
    Oben stand auch #{a + 1}

Aufeinander folgende Strings in einfachen oder doppelten Anführungszeichen werden zu einem einzelnen String-Objekt zusammengezogen.

'Con' "cat" 'en' "ate" » "Concatenate"

Strings werden als Folge von 8-bit-Bytes gespeichert,[Für Japan unterstützt diejcode-Bibliothek einen Satz von Operationen für String, die mit EUC, SJIS oder UTF-8 Codierung geschrieben wurden. Der darunterliegende String wird aber immer noch als Folge von Bytes angesprochen.] und jedes Byte darf jeden der 256 möglichen 8-bit-Werte enthalten, inklusive Null und Newline. Die Ersetzungsmechanismen aus Tabelle 18.2 auf Seite 205 ermöglichen die passende und übertragbare Darstellung von nicht-druckbaren Zeichen.

Jedesmal wenn ein literaler String in einer Zuweisung oder als Parameter benutzt wird, wird ein neues String-Objekt erzeugt.

for i in 1..3
  print 'hello'.id, " "
end
erzeugt:
537685230 537685200 537685170

Die Dokumentation der Klasse String beginnt auf Seite 368.

Ranges

Außerhalb des Kontexts eines Vergleichs-Ausdrucks erzeugen expr..expr und expr...expr Range-Objekte. Bei der Zwei-Punkte-Form gehören die Grenzen dazu, bei der Drei-Punkte-Form die obere Grenze nicht. Details gibts bei der Beschreibung der Klasse Range auf Seite 364. Für andere Einsätze der Ranges siehe auch die Beschreibung für Vergleichs-Ausdrücke auf Seite 224.

Arrays

Literale der Klasse Array werden erzeugt mit einer durch Kommas getrennten Folge von Objekt-Referenzen in eckigen Klammern. Ein eventuelles Komma am Ende dieser Folge wird ignoriert.

arr = [ fred, 10, 3.14, "This is a string", barney("pebbles"), ]

Für Arrays aus Strings gibt es eine Abkürzung, %w, welche durch Leerzeichen getrennte Teile als aufeinander folgende Elemente in ein Array packt. Dabei kann ein gewolltes Leerzeichen mit einem Backslash escaped werden. Dies ist eine Form des allgemein begrenzten Inputs, beschrieben auf den Seiten 202 - 203.

arr = %w( fred wilma barney betty great\ gazoo )
arr » ["fred", "wilma", "barney", "betty", "great gazoo"]

Hashes

Ein lliteraler Ruby-Hash wird erzeugt mit einer Liste von Schlüssel/Wert-Paaren in geschweiften Klammern, mit entweder einem Komma oder => zwischen Schlüssel und Wert. Ein Komma am Schluss wird ignoriert.

colors = { "red"   => 0xf00,
           "green" => 0x0f0,
           "blue"  => 0x00f
         }

Die Schlüssel und/oder Werte müssen nicht den selben Typ haben.

Bedingungen für den Hash-Schlüssel

Die einzige Einschränkung für einen Hash-Schlüssel ist, dass er auf die Nachricht hash mit einem Hash-Wert antworten muss, und der Hash-Wert zu einem gegebenen Schlüssel darf sich nicht ändern. Das bedeutet, dass gewisse Klassen (wie etwa Array und Hash, nach derzeitigem Stand) nicht vernünftig als Schlüssel benutzt werden können, weil ihr Hash-Wert je nach Inhalt wechseln kann.

Wenn man eine externe Referenz auf ein Objekt hält, das als Schlüssel benutzt wird, und dann mit dieser Referenz das Objekt tauscht und so den Hash-Wert ändert, dann kann die Hash-Auswahl über diesen Schlüssel nicht mehr funktionieren.

Weil Strings die gebräuchlichsten Schlüssel von allen sind und die String-Inhalte sich häufig ändern, werden String-Schlüssel in Ruby speziell behandelt. Wenn man ein String-Objekt als Hash-Schlüssel benutzt, wird der Hash den String intern kopieren und diese Kopie als Schlüssel benutzen. Änderungen an dem Original-String haben dann keine Auswirkungen auf den Hash.

Wenn man eigene Klassen schreibt und Instanzen davon als Hash-Schlüssel benutzt, solte man sicher gehen, dass entweder (a) die Zuordnungen der Schlüssel-Objekte sich nicht mehr ändern, nachdem sie einmal erzeugt wurden oder (b) man jedesmal, wenn sie geändert wurde, die Methode Hash#rehash aufruft.

Symbole

Ein Ruby-Symbol ist die interne Repräsentation eines Namens. Man kann ein Symbol erzeugen, indem man einem Namen einen Doppelpunkt voranstellt. Ein gegebener Name erzeugt immer das selbe Symbol, egal, wie der Name im Programm benutzt wird.

:Object
:myVariable

Andere Sprachen nennen diesen Prozess ``interning'' und die Symbole ``atoms.''

Reguläre Ausdrücke

Literale von regulären Ausdrücken sind Objekte vom Typ Regexp. Sie können erzeugt werden durch Aufruf des Regexp.new-Konstruktors oder durch Angeben der literalen Formen /pattern/ und %r{pattern}. Das %r-Konstrukt ist wieder eine Form des allgemein begrenzten Inputs (beschrieben auf den Seiten 202 -203).

/pattern/
/pattern/options
%r{pattern}
%r{pattern}options
Regexp.new( 'pattern' [, options] )

Optionen für Reguläre Ausdrücke

Ein regulärer Ausdruck kann eine oder mehrere Optionen enthalten, die das Vergleichsverhalten des Musters regelt. Wenn man Literale zum Erzeugen der Regexp-Objekte benutzt, bestehen die Optionen aus einem oder mehreren Zeichen direkt nach dem Ende-Zeichen. Wenn man Regexp.new benutzt, dann sind die Optionen Konstanten als zweiter Parameter des Konstruktors.

i Groß/Kleinschreibung egal. Beim Vergleich wird die Groß/Keinschreibung ignoriert. Das passiert auch, wenn die globale Variable $= gesetzt ist
o Einmal ersetzen. Alle #{...}-Ersetzungen in diesem speziellen regulären Ausdruck werden nur einmal ausgeführt, beim ersten Aufruf. Sonst werden die Ersetzungen jedes Mal ausgeführt, wenn das Literal ein Regexp-Objekt erzeugt.
m Mehrzeilen-Modus. Normalerweise passt ``.'' auf jedes Zeichen außer Newline. Mit der /m-Option passt ``.'' auf wirklich jedes Zeichen.
x Erweiterter Modus. Komplexe reguläre Ausdrücke können schwierig zu lesen sein. Mit der `x'-Option kann man Leerzeichen, Newlines und Kommentare in das Muster einfügen, um es lesbarer zu machen.

Muster für reguläre Ausdrücke

reguläre Zeichen
Alle Zeichen außer ., |, (, ), [, \, ^, {, +, $, * und ? passen auf sich selber. Um eines von diesen Zeichen zu erfassen stellt man einen Backslash voran.

^
Erfasst den Zeilenanfang.

$
Erfasst das Zeilenende.

\A
Erfasst den Anfang des Strings.

\z
Erfasst das Ende des Strings.

\Z
Erfasst das Ende des Strings außer der String hört mit einem ``\n'' (Newline) auf, dann erfasst es die Stelle direkt davor.

\b, \B
Erfasst eine Wortgrenze bzw. eben gerade nicht.

[characters]
Passt auf jedes einzelne Zeichen zwischen den Klammern. Die Zeichen |, (, ), [, ^, $, * und ?, die sonst eine spezielle Bedeutung haben, verlieren diese hier. Die Zeichenfolgen \nnn, \xnn, \cx, \C-x, \M-x und \M-\C-x besitzen die Bedeutung wie in Tabelle 18.2 auf Seite 205 angegeben. Die Folgen \d, \D, \s, \S, \w und \W sind Abkürzungen für Gruppen von Zeichen, wie in Tabelle 5.1 auf Seite 62 anbegeben. Die Folge c1-c2 repräsentiert alle Zeichen zwischen c1 und c2, Grenzen inklusive. Die literalen Zeichen ] oder - müssen direkt nach der öffnenden Klammer kommen. Ein Aufwärtspfeil (^), der direkt nach der öffnenden Klammer kommt, negiert den Sinn des Musters, das Muster erfasst dann alle Zeichen, die nicht in der Zeichenklasse sind.

\d, \s, \w
Dies sind Abkürzungen für Zeichenklassen, die Ziffern, Whitespaces bzw. Wort-Zeichen erfassen. \D, \S und \W erfassen Zeichen, die keine Ziffern, Whitespaces bzw. Wortzeichen sind. Diese Abkürzungen werden in Tabelle 5.1 auf Seite 62 zusammengefasst.

. (Punkt)
Falls außerhalb von eckigen Klammern: erfasst jedes Zeichen außer Newline. (Mit der /m-Option auch Newline).

re*
Erfasst null oder mehr Auftreten von re.

re+
Erfasst ein oder mehr Auftreten von re.

re{m,n}
Erfasst mindestens ``m'' und höchstens ``n'' Auftreten von re.

re?
Erfasst null oder ein Auftreten von re. Die *, + und {m,n} Modifikatoren sind defaultmäßig gierig. Gefolgt von einem Fragezeichen sind sie es nicht mehr.

re1|re2
Erfasst entweder re1 oder re2. | besitzt geringe Priorität.

(...)
Klammern benutzt man zum Zusammenfassen von regulären Ausdrücken. Als Beispiel: Das Muster /abc+/ erfasst einen String mit einem ``a,'' einem ``b'' und einem oder mehreren ``c''s. /(abc)+/ erfasst eine oder mehr Folgen von ``abc''. Klammern benutzt man auch, um die Ergebenisse des Mustervergleichs einzusammeln. Für jede öffnende Klammer speichert Ruby das Ergebnis dieses Teiltreffers bis zur schließenden Klammer als aufzählende Gruppen. Innerhalb des gleichen Musters zeigt \1 auf den Treffer der ersten Gruppe, \2 auf den der zweiten Gruppe und so weiter. Außerhalb des Musters dienen die speziellen Variablen $1, $2und so weiter dem gleichen Zweck.

Ersetzungen

#{...}
Ersetzt einen Ausdruck, wie bei Strings. Defaultmäßig wird die Ersetzung bei jeder Auswertung des Literals des regulären Ausdrucks durchgeführt. Mit der /o-Option nur beim ersten Mal.

\0, \1, \2, ... \9, \&, \`, \', \+
Ersetzt den Wert, der vom nten gruppierten Unterausdruck erfasst wurde, bzw. vom gesamten Ausdruck, vorausgehendem oder nachfolgendem Text oder der höchsten Gruppe.

Erweiterungen

Wie bei Perl oder Python unterstützt Ruby auch einige Erweiterungen der traditionellen regulären Ausdrücke von Unix. All diese Erweiterungen stehen zwischen (?  und ). Die Klammern dieser Erweiterungen sind auch Gruppen, aber sie erzeugen keine Rückwärtsreferenzen: sie haben keinen Einfluss auf \1 und $1 usw.

(?# >Kommentar)
Setzt einen Kommentar in das Muster. Der Inhalt wird beim Muster-Vergleich ignoriert.

(?:re)
Fasst re in einer Gruppe zusammen, ohne Rückwärtsreferenzen zu erzeugen. Das ist oft nützlich, wenn man einen Satz von Anweisungen gruppieren muss, aber den Wert von $1 und so nicht ändern will. In dem folgenden Beispiel erfassen beide Muster ein Datum mit entweder Doppelpunkten oder Leerzeichen zwischen Monat, Tag und Jahr. Die erste Form speichert die Trennzeichen in $2 und $4, die zweite nicht.

date = "12/25/01"
date =~ %r{(\d+)(/|:)(\d+)(/|:)(\d+)}
[$1,$2,$3,$4,$5] » ["12", "/", "25", "/", "01"]
date =~ %r{(\d+)(?:/|:)(\d+)(?:/|:)(\d+)}
[$1,$2,$3] » ["12", "25", "01"]

(?=re)
Erfasst re an dieser Stelle, aber benutzt es danach nicht (auch bekannt als ``null-breite positive Vorausschau'')(Na ja, das ist nicht so gut übersetzt, aber es ist übersetzt, der Übersetzter). Damit kann man den Kontext eines Musters mit erfassen, ohne $& zu beeinflussen. Im nächstan Beispiel erfasst die scan-Methode Wörter mit nachfolgendem Komma, ohne dass die Kommas im Ergebnis enthalten sind.

str = "red, white, and blue"
str.scan(/[a-z]+(?=,)/) » ["red", "white"]

(?!re)
Erfasst das, was re nicht erfasst. Benutzt das Gefundene nicht (null-breite negative Vorausschau). Zum Beispiel erfasst /hot(?!dog)(\w+)/ jedes Wort, das die Buchstaben ``hot'' enthält, an denen kein ``dog'' hängt. Zurück geliefert wird das Ende des Wortes in $1.

(?>re)
Verschachtelt einen unabhängigen regulären Ausdruck in einem anderen regulären Ausdruck. Dieser Ausdruck sitzt dann anstelle der gefundenen Position. Wenn er Zeichen beansprucht, so stehen diese dem übergeordneten Ausdruck nicht mehr zur Verfügung. Dieses Konstrukt verhindert deshalb rekursive Suchen, was einen Performance-Vorteil mit sich bringen kann. Zum Beispiel benötigt das Muster /a.*b.*a/ exponetiell viel Zeit, wenn es auf einen String angewandt wird, der ein ``a'' enthält, gefolgt von mehreren ``b''s, aber ohne abschließendem ``a''. Dies kann vermieden werden, wenn man einen verschachtelten regulären Ausdruck benutzt /a(?>.*b).*a/. Bei dieser Form verbraucht der innere Ausdruck den ganzen Input-String bis hin zum letzten ``b''. Wenn dann auf das abschließende ``a'' geprüft wird, besteht keine Notwendigkeit mehr für eine Rekursion, und der Vergleich ist sofort beendet.

require "benchmark"
include Benchmark
str = "a" + ("b" * 5000)
bm(8) do |test|
  test.report("Normal:") { str =~ /a.*b.*a/ }
  test.report("Nested:") { str =~ /a(?>.*b).*a/ }
end
erzeugt:
              user     system      total        real
Normal:   2.620000   0.000000   2.620000 (  2.651591)
Nested:   0.000000   0.000000   0.000000 (  0.000883)

(?imx)
Schaltet die dazugehörige ``i,''- ``m,''- oder ``x''-Option an. Der Effekt ist auf die Gruppe beschränkt, falls es innerhalb einer Gruppe benutzt wird.

(?-imx)
Schaltet die dazugehörige ``i,''- ``m,''- oder ``x''-Option ab.

(?imx:re)
Schaltet die dazugehörige ``i,''- ``m,''- oder ``x''-Option für re an.

(?-imx:re)
Schaltet die dazugehörige ``i,''- ``m,''- oder ``x''-Option für re ab.

Namen

Ruby-Namen werden zur Referenzierung von Konstanten, Variablen, Methoden, Klassen und Modulen benutzt. Das erste Zeichen eines Namens zeigt Ruby die gewünschte Verwendung. Bestimmte Namen, aufgelistet in Tabelle 18.3 auf Seite 212, sind reservierte Namen und sollten nicht als Variablen-, Methoden-, Klassen- oder Modul-Namen benutzt werden.

Reservierte Wörter

__FILE__ and def end in or self unless
__LINE__ begin defined? ensure module redo super until
BEGIN break do false next rescue then when
END case else for nil retry true while
alias class elsif if not return undef yield

In dieser Beschreibung bezeichnet Kleinbuchstabe die Zeichen ``a'' bis ``z'', aber auch den Underscore ``_''. Großbuchstabe bezeichnet ``A'' bis ``Z,'' und Ziffer bezeichnet ``0'' bis ``9.'' Namen-Zeichen bezeichnet jede Kombination von Klein- und Großbuchstaben und Ziffern.

Eine lokale Variable besteht aus einem Kleinbuchstaben gefolgt von Namen-Zeichen.

fred  anObject  _x  three_two_one

Der Name einer Instanz-Variablen beginnt mit einem ``at''-Zeichen (``@'') gefolgt von einem Groß- oder Kleinbuchstaben, optional gefolgt von Namen-Zeichen.

@name  @_  @Size

Der Name einer Klassen-Variablen beginnt mit zwei ``at''-Zeichen (``@@'') gefolgt von einem Groß- oder Kleinbuchstaben, optional gefolgt von Namen-Zeichen.

@@name  @@_  @@Size

Der Name einer Konstanten beginnt mit einem Großbuchstaben gefolgt von Namen-Zeichen. Klassen- und Modul-Namen sind Konstanten und folgen daher dieser Regel für Konstanten-Namen. Nach Konvention werden normalerweise Konstanten nur mit Großbuchstaben und Underscores geschrieben.

module Math
  PI = 3.1415926
end
class BigBlob

Globale Variablen, sowie einige spezielle System-Variablen, beginnen mit einem Dollar-Zeichen (``$'') gefolgt von Namen-Zeichen. Zusätzlich gibt es noch zwei-Zeichen-Variablen, deren zweites Zeichen ein Interpunktions-Zeichen ist. Diese vordefinierten Variablen werden ab Seite 216 aufgelistet. Schließlich kann man eine globale Variable erschaffen mit ``$-'' gefolgt von jedem einzelnen Zeichen.

$params  $PROGRAM  $!  $_  $-a  $-.

Methoden-Namen werden in dem auf Seite 227 beginnenden Abschnitt beschrieben.

Variablen/Methoden-Mehrdeutigkeit

Wenn Ruby in einem Ausdruck auf einen Namen wie ``a'' stößt, muss es erkennen, ob das eine Referenz auf eine lokale Variable oder ein Methoden-Aufruf ohne Parameter ist. Um das zu entscheiden, benutzt Ruby eine heuristische Methode. Während Ruby die Quell-Datei liest, merkt es sich alle Symbole, die eine Zuweisung erhalten haben. Wenn es schließlich zu einem Symbol kommt, das sowohl eine Variable als auch eine Methodenaufruf sein könnte, schaut es nach, ob es vorher schon eine Zuweisung an dieses Symbol gegeben hat. Falls ja behandelt es dieses Symbol als Variable, sonst als Methodenaufruf. Als etwas pathologisches Beispiel dafür betrachten wir das folgende Code-Fragment, eingeschickt von Clemens Hintze.

def a
  print "Function 'a' called\n"
  99
end

for i in 1..2   if i == 2     print "a=", a, "\n"   else     a = 1     print "a=", a, "\n"   end end
erzeugt:
a=1
Function 'a' called
a=99

Während des Durchlaufs sieht Ruby die Benutzung von ``a'' in der ersten Print-Anweisung und nimmt an, dass das ein Methodenaufruf ist, weil es noch keine Zuweisung an ``a'' gesehen hat. Wenn es dann zu der zweiten Print-Anweisung kommt, hat es eine Zuweisung gesehen und behandelt ``a'' deshalb als Variable.

Beachte dass die Zuweisung nicht ausgeführt werden muss --- Ruby muss sie nur vorher gesehen haben. Dieses Programm löst keinen Fehler aus.

a = 1 if false; a

Variablen und Konstanten

Ruby-Variablen und -Konstanten beinhalten Referenzen auf Objekte. Variablen selber haben keinen eingebauten Typ. Stattdessen ist der Typ einer Variable allein durch Nachrichten definiert, auf die das von der Variablen referenzierte Objekt reagiert. [ Wenn wir sagen, dass Variablen nicht typisiert sind, so meinen wir, dass eine Variable zu verschiedenen Zeiten Objekte verschiedenen Typs referenzieren kann.]

Eine Ruby-Konstante ist ebenso eine Referenz auf ein Objekt. Konstanten werden erzeugt, wenn ihnen zum ersten Mal etwas zugewiesen wird (normalerweise in einer Klassen- oder Moduldefinition). In Ruby kann man aber, anders als in weniger flexiblen Sprachen, den Wert einer Konstanten ändern, obwohl dies eine Warnung erzeugt.

MY_CONST = 1
MY_CONST = 2   # generates a warning
erzeugt:
prog.rb:2: warning: already initialized constant MY_CONST

Beachte: Auch wenn man Konstanten nicht ändern sollte, so kann man den internen Status der referenzierten Objekte verändern.

MY_CONST = "Tim"
MY_CONST[0] = "J"   # alter string referenced by constant
MY_CONST » "Jim"

Durch Zuweisungen kann man Objekten Alias-Namen geben, wodurch das selbe Objekt verschiedene Namen erhält.

Gültigkeitsbereich von Konstanten und Variablen

Auf Konstanten, die innerhalb einer Klasse oder eines Moduls definiert wurden, kann ohne weiteres überall in dieser Klasse oder diesem Modul zugegriffen werden. Außerhalb der Klasse oder des Moduls muss man den Bereichsoperator ``::'' benutzen, mit einem Ausdruck, der das Klassen- oder Modul-Objekt zurückliefert, vorne dran. Konstanten, die außerhalb jeder Klasse oder Moduls definiert wurden, kann man ohne Zusätze oder mit dem Bereichsoperator ``::'' ohne Vorsatz ansprechen. Konstanten dürfen nicht in Methoden definiert werden.

OUTER_CONST = 99
class Const
  def getConst
    CONST
  end
  CONST = OUTER_CONST + 1
end
Const.new.getConst » 100
Const::CONST » 100
::OUTER_CONST » 99

Globale Variablen sind im gesamten Programm verfügbar. Jede Referenz auf eine spezielle globale Variable liefert das selbe Objekt zurück. Wenn man eine nicht initialisierte globale Variable referenziert, erhält man nil.

Klassen-Variablen sind in der gesamten Klasse oder im Modul-Rumpf verfügbar. Eine Klassen-Variable muss vor der Benutzung initialisiert werden. Eine Klassen-Variable wird von allen Instanzen einer Klasse gemeinsam benutzt.

class Song
  @@count = 0
  def initialize
    @@count += 1
  end
  def Song.getCount
    @@count
  end
end

Klassen-Variablen gehören zu der innersten der umschließenden Klassen oder Module. Klassen-Variablen, die auf dem obersten Level benutzt werden, sind in der Klasse Object definiert und verhalten sich wie globale Variablen. Klassen-Variablen in Singleton-Methoden gehören zu dem Empfänger, wenn dieser eine Klasse oder Modul ist; ansonsten gehören sie zu der Klasse des Empfängers.

class Holder
  @@var = 99
  def Holder.var=(val)
    @@var = val
  end
end

a = Holder.new def a.var   @@var end

Holder.var = 123
a.var » 123

Instanz-Variablen sind innerhalb von Instanz-Methoden im gesamten Klassenrumpf verfügbar. eEnn man eine nicht initialisierte Instanz-Variable referenziert, erhält man nil. Jede Instanz einer Klasse besitzt einen eigenen Satz von Instanz-Variablen. Instanz-Variablen sind in Klassen-Methoden nicht verfügbar.

Lokale Variablen sind insofern einzigartig, dass ihr Gültigkeitsbereich statisch festgelegt wird, auch wenn ihre Existenz erst dynamisch hervorgerufen wird.

Eine lokale Variable wird erst dann dynamisch erzeugt, wenn ihr das erste Mal während des Programmablaufs ein Wert zugewiesen wird. Auf der anderen Seite wird der Gültigkeitsbereich einer lokalen Variable statisch festgelegt: der unmittelbar umschließende Block, Methodendefinition, Klassendefinition, Moduldefinition oder das top-level Programm. Wenn man eine Variable referenziert, die zwar im Gültigkeitsbereich existiert aber der noch nichts zugewiesen wurde, erhält man eine NameError-Exception.

Lokale Variablen mit dem selben Namen sind unterschiedliche Variablen, wenn sie in unterschiedlichen Gültigkeitsbereichen auftauchen.

Methoden-Parameter werden als zu dieser Methode lokale Variablen angesehen.

Block-Parametern wird ihr Wert zugewiesen, wenn der Block aufgerufen wird.

a = [ 1, 2, 3 ]
a.each { |i|  puts i  }  # i local to block
a.each { |$i| puts $i }  # assigns to global $i
a.each { [email protected]| puts @i }  # assigns to instance variable @i
a.each { |I|  puts I  }  # generates warning assigning to constant
a.each { |b.meth| }      # invokes meth= in object b
sum = 0
var = nil
a.each { |var| sum += var }  # uses sum and var from enclosing scope

Falls eine lokale Variable (inklusive ein Block-Parameter) innerhalb des Blocks zum ersten Mal etwas zugewiesen bekommt. so ist sie lokal zu diesem Block. Wenn aber zur Ausführungszeit des Blocks eine Variable mit dem selben Namen bereits existiert, dann erbt der Block diese Variable.

Ein Block nimmt den Satz an lokalen Variablen mit, der bei seiner Erzeugung existent war. Das bildet einen Teil seiner Bindung. Beachte, dass der Block, auch wenn die Bindung an die Variablen an diesem Punkt festgelegt wird, bei Ausführung Zugriff auf die aktuellen Werte der Variablen hat. Die Bindung erhält diese Variablen auch, wenn der umgebende Original-Gültigkeitsbereich längst zerstört ist.

Die Rümpfe von while-, until- und for-Schleifen gehören zum Gültigkeitsbereich, der sie umgibt; vorher schon existierende Variablen können in der Schleife benutzt werden und neu erzeugte Variablen sind hinterher außerhalb der Schleifen verfügbar.

Vordefinierte Variablen

Die folgenden Variablen sind im Ruby-Interpreter vordefiniert. In dieser Beschreibung bedeutet {}, dass die Variablen nur gelesen werden können; ein Error wird ausgelöst, wenn ein Programm versucht, nur lesbare Variablen zu verändern. Es ist allerdings auch unwahrscheinlich, dass man irgenwo im Programm versucht, die Bedeutung von true zu ändern (außer man ist ein Politiker). Einträge mit {} sind thread local.

Viele globale Variablen sehen aus wie die Flüche von Snoopy: $_, $!, $& und so weiter. Das hat historische Gründe, weil die meisten dieser Variablen-Namen aus Perl stammen. Wem das Merken all dieser seltsamen Zeichen schwer fällt, sollte sich die Bibliotheks-Datei ``English'' auf Seite ??? ansehen, die den gebräuchlichsten globalen Variablen eher beschreibende Namen gibt.

In den folgenden Tabellen von Variablen und Konstanten zeigen wir den Variablen-Namen, den Typ des referenzierten Objekts und eine Beschreibung.

Informationen über Exceptions

$! Exception Das Exception-Objekt, das an raise übergeben wurde. {}

$@ Array Der von der letzten Exception erzeugte Stack-Backtrace. Siehe Kernel#caller auf Seite 417. {}

Variablen zur Muster-Erfassung

Diese Variablen (außer $=) werden nach einem erfolglosen Mustervergleich auf nil gesetzt.

$& String Der vom letzten erfolgreichen Mustervergleich erfasste String. Diese Variable ist lokal zum aktuellen Gültigkeitsbereich. {}

$+ String Der Inhalt der vom letzten erfolgreichen Mustervergleich erfassten Gruppe. So wird $+ bei "cat" =~/(c|a)(t|z)/ auf ``t'' gesetzt. Diese Variable ist lokal zum aktuellen Gültigkeitsbereich. {}

$` String Der String, der direkt vor dem vom letzten erfolgreichen Mustervergleich erfassten String steht. Diese Variable ist lokal zum aktuellen Gültigkeitsbereich. {}

$' String Der String, der direkt nach dem vom letzten erfolgreichen Mustervergleich erfassten String steht. Diese Variable ist lokal zum aktuellen Gültigkeitsbereich. {}

$= Object Falls auf einen Wert ungleich nil oder false gesetzt, so werden alle Mustervergleiche ohne Berücksichtigung der Groß-klein-Schreibung durchgeführt, String Vergleiche genauso und bei Hash-Werten als String ebenso.

$1 to $9 String Der Inhalt der vom letzten erfolgreichen Mustervergleich erfassten Gruppen. So wird $1 bei "cat" =~/(c|a)(t|z)/ auf ``a'' gesetzt und $2 auf ``t''. Diese Variable ist lokal zum aktuellen Gültigkeitsbereich. {}
$~ MatchData Ein Objekt, das die Ergebnisse eines erfolgreichen Mustervergleichs enthält. Die Variablen $&, $`, $' und $1 bis $9 werden alle aus $~ abgeleitet. Zuweisungen an $~ ändern den Wert dieser abgeleiteten Variablen. Diese Variable ist lokal zum aktuellen Gültigkeitsbereich. {}

Input/Output-Variablen

$/ String der Input-Record-Separator (defaultmäßig Newline). Mit diesem Wert bestimmen Routinen wie Kernel#gets die Grenzen innerhalb von Aufzählungen. Falls auf nilgesetzt liest gets die ganze Datei.

$-0 String Synonym für $/.

$\ String Dieser String wird an den Output jedes Aufrufs von Methoden wie Kernel#print und IO#write gehängt. Defaultmäßig auf nilgesetzt.

$, String Der Trennungs-String, der zwischen die Parameter von Methoden wie Kernel#print und Array#join gesetzt wird. Defaultmäßig nil, also kein Text.

$. Fixnum Die Nummer der letzten von der aktuellen Input-Datei gelesenen Zeile.

$; String Das Trennmuster für String#split. Kann von der Kommmandozeile aus mit dem -F-Flag gesetzt werden.

$< Object Dieses Objekt ermöglicht den Zugriff auf die hintereinander gehängten Dateien, die als Kommandozeilen-Argumente mitgeliefert wurden, oder $stdin (falls es keine Argumente gab). $< unterstützt ähnliche Methoden wie ein File object: binmode, close, closed?, each, each_byte, each_line, eof, eof?, file, filename, fileno, getc, gets, lineno, lineno=, pos, pos=, read, readchar, readline, readlines, rewind, seek, skip, tell, to_a, to_i, to_io, to_s genauso wie Methoden aus Enumerable. Die Methode file liefert ein File-Objekt für die aktuell gelesene Datei. Dieses kann sich ändern, während $< sich durch die Dateien aus der Kommandozeile liest. {}

$> IO Das Ziel des Outputs für Kernel#print und Kernel#printf. Defaultwert ist $stdout.

$_ String Die letzte von Kernel#gets oder Kernel#readline gelesene Zeile. Viele string-bezogene Funktionen im KernelModul arbeiten defaultmäßig mit $_. Die Variable ist lokal zum aktuellen Gültigkeitsbereich. {}

$defout IO Synonym für $>.

$-F String Synonym für $;.

$stderr IO Der aktuelle Standard-Error-Kanal.

$stdin IO Der aktuelle Standard-Input-Kanal.

$stdout IO Der aktuelle Standard-Output-Kanal.

Variablen der Ausführungs-Umgebung

$0 String Der Name des gerade ausgeführten top-level Ruby-Programms. Üblicherweise der Dateiname des Programms. Auf einigen Systemen ändert eine Zuweisung an diese Variable den Namen des Prozesses, der (zum Beispiel) vom ps(1)-Kommando geliefert wird.

$* Array Ein Array mit Strings, die die Kommandozeilen-Optionen vom Aufruf des Programms enthalten. Die Optionen für den Rupy-Interpreter sind aber schon entfernt. {}

$" Array Ein Array mit Dateinamen für Module, die von require geladen wurden. {}

$$ Fixnum Die Prozessnummer des ausgeführten Programms. {}

$? Fixnum Der Exit-Status des letzten terminierten Kind-Prozesses. {}

$: Array Ein Array mit Strings, wobei jeder String ein Verzeichnis mit Ruby-Skripten und binären Erweiterungen bezeichnet, das von den load- und require-Methoden durchsucht wird. Der Anfangswert ist der mit der -I-Kommandozeilen-Option übergebene Wert, gefolgt von der bei der Installation definierten Standard-Bibliothek und gefolgt vom aktuellen Verzeichnis (``.''). Mit dieser Variablen kann man aus einem Programm heraus den Default-Suchpfad ändern; üblicherweise benutzten Programme $: << dir, um dir an den Pfad anzuhängen. {}

$-a Object True, falls die -a-Option in der Kommandozeile angegeben wurde. {}

$-d Object Synonym für $DEBUG.

$DEBUG Object Wird auf true gesetzt, falls die -d-Kommandozeilen-Option angegeben wurde.

__FILE__ String Der Name der aktuellen Quell-Datei. {}

$F Array Das Array mit der aufgesplitteten Input-Zeile, falls die -a-Kommandozeilen-Option benutzt wurde.

$FILENAME String Der Name der aktuellen Input-Datei. Äquivalent zu $<.filename. {}

$-i String Falls der Einfüge-Modus fürs Editieren eingeschaltet ist (vielleicht mit der -i-Kommandozeilen-Option), dann enthält $-i die Erweiterung, die beim Erzeugen einer Bakup-Datei benutzt wird. Das Schreiben eines Wertes in $-i schaltet den Einfüge-Modus ein. Siehe Seite 138.

$-I Array Synonym für $:. {}

$-K String Aktiviert das Mehr-Byte Code-System für Strings und reguläre Ausdrücke. Äquivalent zur -K-Kommandozeilen-Option. Siehe Seite 139.

$-l Object Wird auf true gesetzt, wenn die -l-Option (die die Zeilen-Ende-Verarbeitung aktiviert) in der Kommandozeile enthalten ist. Siehe Seite 139. {}

__LINE__ String Die aktuelle Zeilennummer im Quelltext. {}

$LOAD_PATH Array Ein Synonym für $:. {}

$-p Object Wird auf true gesetzt, falls die -p-Option (die eine implizite while gets ... end-Schleife um dein Programm setzt) in der Kommandozeile enthalten ist. Siehe Seite 139. {}

$SAFE Fixnum Der aktuelle Sicherheits-Level (siehe Seite 258). Der Wert dieser Variablen darf durch Zuweisung niemals verringert werden. {}

$VERBOSE Object Wird auf true gesetzt, falls die -v, --version oder -w-Option in der Kommandozeile angegeben ist. Das Setzen dieser Option auf true veranlasst den Interpreter und einige Bibliotheks-Routinen, zusätzliche Informationen zu melden.

$-v Object Synonym für $VERBOSE.

$-w Object Synonym für $VERBOSE.

Standard Objekte

ARGF Object Ein Synonym für $<.

ARGV Array Ein Synonym für $*.

ENV Object Ein hash-ähnliches Objekt, das die Umgebungsvariablen des Programms enthält. Eine Instanz der Klasse Object, ENV besitzt den vollen Satz an Hash-Methoden. Man benutzt es, um den Wert einer Umgebungsvariablen abzufragen oder zu setzen, wie in ENV["PATH"] und ENV['term']="ansi".

false FalseClass Singleton-Instanz der Klasse FalseClass. {}

nil NilClass Die Singleton-Instanz der Klasse NilClass. Der Wert von uninitialisierten Instanzen und globalen Variablen. {}

self Object Der Empfänger (Object) der aktuellen Methode. {}

true TrueClass Singleton-Instanz der Klasse TrueClass. {}

Globale Konstanten

Die folgenden Konstanten werden vom Ruby-Interpreter definiert.

DATA IO Wenn die Datei des Hauptprogramms die Direktive __END__enthält, dann wird die Konstante DATA so initialisiert, dass sie beim Lesen die Zeilen liefert, die auf __END__ folgen.

FALSE FalseClass Synonym für false.

NIL NilClass Synonym für nil.

RUBY_PLATFORM String Die Kennung der Plattform, auf der dieses Programm läuft. Dieser String hat die selbe Form wie die Plattformkennung, die vom GNU-Configure-Programm benutzt wird (was ja auch kein Zufall ist).

RUBY_RELEASE_DATE String Das Datum dieses Releases.

RUBY_VERSION String Die Versionsnummer des Interpreters.

STDERR IO Der aktuelle Standard-Error-Kanal für dieses Programm. Der Anfangswert für $stderr.

STDIN IO Der aktuelle Standard-Input-Kanal für dieses Programm. Der Anfangswert für $stdin.

STDOUT IO Der aktuelle Standard-Output-Kanal für dieses Programm. Der Anfangswert für $stdout.

TOPLEVEL_BINDING Binding Ein Binding-Objekt, das die Bindung auf Rubys Top-Level repräsentiert --- das Level, auf dem Programme anfänglich ausgeführt werden.

TRUE TrueClass Synonym für true.

Ausdrücke

Einfache Terme

Einfache Terme in einem Ausdruck dürfen eines der Folgenden sein.

Operator-Ausdrücke

Ruby-Operatoren (nach Priorität geordnet)
Methode Operator Beschreibung
Y [ ] 0[ ]= Referenz auf ein Element, Satz von Elementen
Y ** Exponentiation
Y ! 0~ 0+ 0-- Not, Komplement, unäres Plus und Minus (die Methoden-Namen für die letzen beiden sind +@ und -@)
Y * 0/ 0% Multiplizieren, Dividieren und Modulo
Y + 0-- Plus und Minus
Y >> 0<< Rechter und linker Shift
Y & Bitweises `and'
Y ^ 0| Bitweises exklusives `or' und normales `or'
Y <= 0< 0> 0>= Vergleichs-Operator
Y <=> 0== 0=== 0!= 0=~ 0!~ Gleichheits- und Muster-Vergleichs-Operator (!= und !~ dürfen nicht als Methoden definiert werden)
&& logisches `and'
|| Logisches `or'
.. 0... Bereich(inklusive und exklusive rechter Rand)
? : Ternäres if-then-else
= 0%= 0{ /= 0--= 0+= 0|=&= 0>>= 0<<= 0*= 0&&= 0||= 0**= Zuweisung
defined? Test ob Symbol definiert ist
not Logische Negation
or 0and Logischer Vergleich
if 0unless 0while 0until Ausdrucks-Modifikator
begin/end Block-Ausdruck

Ausdrücke können durch Operatoren kombiniert werden. Tabelle 18.4 auf Seite 221 zeigt die Ruby-Operatoren nach Priorität sortiert. Die Operatoren mit einem Y in der Methoden-Spalte sind als Methoden implementiert und können überschrieben werden.

Mehr über Zuweisungen

Der Zuweisungsoperator weist einem oder mehreren rvalues einem oder mehreren lvalues zu. Was mit dieser Zuweisung gemeint ist, hängt von jedem einzelnen lvalue ab.

Wenn der lvalue ein Variablen- oder Konstanten-Name ist, erhält diese Variable oder Konstante eine Referenz auf den entsprechenden rvalue.

a, b, c = 1, "cat", [ 3, 4, 5 ]

Ist der lvalue ein Objekt-Attribut, so wird die dazugehörende Setting-Methode im Empfänger aufgerufen und der rvalue als Parameter übergeben.

anObj = A.new
anObj.value = "hello"   # äquivalent zu anObj.value=("hello")

Ist der lvalue eine Array-Element-Referenz, so ruft Ruby den Element-Zuweisungs-Operator (``[]='') im Empfänger auf und übergibt als Parameter alle Indize, die in den Klammern stehen, gefolgt vom rvalue. Dies wird in Tabelle 18.5 auf Seite 222 gezeigt.

Zuordnung von Element-Referenz zu Methoden-Aufruf

Element-Referenz wirklicher Methoden-Aufruf
anObj[] = "one" anObj.[]=("one")
anObj[1] = "two" anObj.[]=(1, "two")
anObj["a", /^cat/] = "three" anObj.[]=("a", /^cat/, "three")

Parallele Zuweisung

Eine Zuweisung kann ein oder mehrere lvalues und ein oder mehrere rvalues haben. Dieser Abschnitt erklärt, wie Ruby die Zuweisung handhabt, wenn verschiedene Kombinationen auftreten.

  1. Wenn dem letzten rvalue ein Sternchen vorangestellt ist und es ist ein Objekt der Klasse Array, dann wird der rvalue ersetzt durch die Elemente des Arrays, wobei jedes Element wieder einen eigenen rvalue ergibt.
  2. Wenn die Zuweisung mehrere lvalues und einen rvalue enthält, dann wird der rvalue in ein Array konvertiert, dieses wird in einen Satz von rvalues expandiert wie unter (1) beschrieben.
  3. Aufeinander folgende rvalues werden den lvalues zugewiesen. Diese Zuweisung geschieht tatsächlich parallel, so dass (zum Beispiel) a,b=b,a die Werte in ``a'' und ``b'' austauscht.
  4. Wenn mehr lvalues als rvalues vorhanden sind, bekommen die überzähligen nil zugewiesen.
  5. Wenn mehr rvalues als lvalues vorhanden sind, werden die überzähligen ignoriert.
  6. Diese Regeln werden leicht abgewandelt, wenn dem letzten lvalue ein Sternchen vorangestellt ist. Dieser lvalue erhält dann immer ein Array während der Zuweisung. Das Array besteht aus dem rvalue, der normalerweise diesem lvalue zugewiesen worden wäre, gefolgt von den restlichen rvalues (falls es noch welche gibt).
  7. Wenn der lvalue eine geklammerte Aufzählung ist, wird er wie eine verschachtelte Zuweisung behandelt und diese Aufzählung bekommt den dazugehörigen rvalue zugeweisen, wie in diesen Regeln beschrieben.

Beispiele hierzu gibt es in der Anleitung ab Seite 77.

Block-Ausdrücke

begin
  body
end

Ausdrücke dürfen mit begin und end zusammengefasst werden. Der Wert dieses Block-Ausdrucks ist der letzte innerhalb des Blocks berechnete Wert.

Block-Ausdrücke spielen auch im Exception-Handling eine Rolle, das wird ab Seite 237 beschrieben.

Boolsche Ausdrücke

Boolsche Ausdrücke werden als Wahrheitswert berechnet. Einige Ruby-Konstrukte (besonders Bereiche) verhalten sich anders, wenn sie in einem Boolschen Ausdruck ausgewertet werden.

Wahrheits-Werte

In Ruby gibt es die vordefinierten globalen Werte false und nil. Beide Werte werden in einem boolschen Kontext wie falsch behandelt. Alle anderen Werte werden wie wahr behandelt.

And, Or, Not und Defined?

Der and- und der &&-Operator wertet den ersten Operanden aus. Falls er falsch ist, gibt der Ausdruck falsch zurück; sonst gibt der Asudruck den Wert des zweiten Operators zurück.

expr1  and  expr2expr1  &&   expr2

Der or- und der ||-Operator wertet der ersten Operanden aus. Falls er wahr ist, gibt der Ausdruck wahr zurück; sonst gibt der Ausdruck den Wert des zweiten Operators zurück.

expr1  or  expr2expr1  ||  expr2

Der not- und der !-Operator werten seinen Ausdruck aus. Falls er wahr ist, gibt der Ausdruck wahr zurück. Falls er falsch ist, gibt der Ausdruck falsch zurück.

Die ausgeschriebenen Formen dieser Operatoren (and, or und not) besitzen eine geringere Priorität als die entsprechenden Symbol-Formen (&&, || und !). Siehe auch Tabelle 18.4 auf Seite 221.

Der defined?-Operator gibt nil zurück, wenn sein Argument, das frei wählbar ist, nicht definiert ist. Sonst gibt er eine Beschreibung dieses Arguments zurück. Beispiele gibts auf Seite 80 in der Anleitung.

Vergleichs-Operatoren

In der Ruby-Syntax werden die Vergleichsoperatoren ==, ===, <=>, <, <=, >, >=, =~ sowie die Standard-Methoden eql? und equal? definiert (siehe Tabelle 7.1 auf Seite 81). All diese Operatoren sind als Methoden implementiert. Obwohl diese Operatoren intuitiv zu erfassen sind, bleibt es doch der implementiernden Klasse überlassen, sie mit vernünftiger Bedeutung zu füllen. (Der Übersetzer: So ist ist es zum Beispiel völliger Unsinn, ein > für komplexe Zahlen zu definieren!) Die Bibliotheks-Referenz beginnt auf Seite 279 und beschreibt die Bedeutung der Vergleiche für die eingebauten Klassen. Das Modul Comparable bietet Unterstützung beim Implementieren der Operatoren ==, <, <=, >, >= und der Methode between? in Hinblick auf <=>. Der Operator === wird in case-Ausdrücken benutzt, beschrieben auf Seite 225.

Sowohl == als auch =~ besitzen negierte Formen , != und !~. Diese werden von Ruby während der Syntax-Analyse konvertiert: a!=b wird abgebildet auf !(a==b) und a!~b wird abgebildet auf !(a =~b). Für != and !~ gibt es keine entsprechenden Methoden!

Bereiche in Boolschen Ausdrücken

if  expr1 .. expr2
while  expr1 ... expr2

Ein in einem boolschen Ausdruck benutzter Bereich funktioniert wie ein Flip-Flop. Er hat zwei Zustände, gesetzt und ungesetzt. Bei jedem Aufruf läuft der Bereich durch die Zustandsauswertung, wie in Figur 18.1 auf Seite 225 zu sehen. Der Bereich gibt true zurück, falls er im gesetzten Status ist, sonst false.

Die Zwei-Punkte-Form verhält sich leicht anders als die Drei-Punkte-Form. Wenn die Zwei-Punkte-Form zum ersten Mal den Übergang von ungesetzt nach gesetzt vollzieht, wertet sie direkt die End-Bedingung aus und macht den Übergang dem entsprechend. Das heißt, wenn bei einem Aufruf sowohl expr1 und expr2 wahr sind, dann beendet die Zwei-Punkte-Form den Aufruf im ungesetzten Zustand. Trotzdem gibt sie bei diesem Aufruf noch true zurück.

Der Unterschied ist im folgenden Code ersichtlich:

a = (11..20).collect {|i| (i%4 == 0)..(i%3 == 0) ? i : nil}
a » [nil, 12, nil, nil, nil, 16, 17, 18, nil, 20]
a = (11..20).collect {|i| (i%4 == 0)...(i%3 == 0) ? i : nil}
a » [nil, 12, 13, 14, 15, 16, 17, 18, nil, 20]

Zustandsänderungen bei boolschen Bereichen

Figur 18.1 Zustandsänderungen bei boolschen Bereichen

Reguläre Ausdrücke in boolschen Ausdrücken

Falls ein einzelner regulärer Ausdruck als boolscher Ausdruck auftaucht, wird er mit dem Wert der Variablen $_ verglichen.

if /re/ ...
ist äquivalent mit

if $_ =~ /re/ ...

If und Unless Ausdrücke

if boolean-expression[then]body
elsif boolean-expression[then]body
else
  body
end

unless boolean-expression[then]body
else
  body
end

Das Schlüsselwort then trennt den Rumpf von der Bedingung. Es wird nicht benötigt, falls der Rumpf auf einer neuen Zeile beginnt. Der Wert eines if- oder unless-Ausdrucks ist der Wert des letzten ausgewerteten Ausdrucks, egal in welchem Rumpf.

If und Unless Modifikatoren

expression if     boolean-expression

expression unless boolean-expression

wertet expression nur dann aus, wenn boolean-expression true ist (bzw. false ist bei unless).

Ternärer Operator

boolean-expression ? expr1 : expr2

gibt expr1 zurück, falls boolean expression wahr ist und expr2 sonst.

Case-Ausdruck

case target
  when [comparison]+[then]body
  when [comparison]+[then]body
  ...
[ else
    body]
 end

Ein Case-Ausdruck sucht nach einem passenden Teil und fängt dabei beim ersten (oben links) Vergleicher (comparison) an, wobei er comparison === target ausführt. Wenn ein Vergleich wahr ergibt, wird die Suche gestoppt und der entsprechende Rumpf wird ausgeführt. case gibt dann den Wert des letzten ausgeführten Ausdrucks zurück. Falls kein Vergleicher (comparison) passt, wird der Else-Zweig ausgeführt, wenn keiner vorhanden ist, gibt case einfach nur nil zurück.

Das Schlüsselwort then trennt den when-Vergleicher vom Rumpf und wird nicht benötigt, wenn der Rumpf auf einer neuen zeile anfängt.

Schleifen

while boolean-expression[do]body
end

führt body null oder mehrere Male aus, solange wie boolean-expression wahr ist.

until boolean-expression[do]body
end

führt body null oder mehrere Male aus, solange wie boolean-expression falsch ist.

In beiden Formen trennt das do das boolean-expression vom body und kann weggelassen werden, falls der Rumpf body auf einer neuen Zeile beginnt.

for [name]+ in expression[do]body
end

Die for-Schleife wird ausgeführt, als wäre sie die folgende each-Schleife, außer dass die im for-Rumpf definierten Variablen auch außerhalb der Schleife verfügbar sind, die in einem Iterator definierten sind das nicht.

expression.each do | [name]+ |
  body
end

loop, das über seinen dazugehörenden Block iteriert, ist kein Sprachkonstrukt --- es ist eine Methode des Moduls Kernel.

While- und Until-Modifikatioren

expression while boolean-expression

expression until boolean-expression

Falls expression irgendwas ist außer ein begin/end-Block, dann wird expression null oder mehrere Male ausgeführt, solange wie boolean-expression true ist (false ist bei until).

Falls expression ein begin/end-Block ist, wird der Block immer mindestens ein Mal ausgeführt.

Break, Redo, Next und Retry

break, redo, next, and retry ändern den normalen Ablauf in einer while, until, for oder über Iterator kontrollierten Schleife.

break beendet die umschließende Schliefe direkt --- die Ausführung geht direkt hinter dem Block weiter. redo wiederholt die Schleife von Anfang an, aber ohne die Bedingung neu zu prüfen oder das nächste Element (bei einem Iterator) zu holen. next springt ans Ende der Schleife ind macht mit dem nächsten Element der Iteration weiter. retry wiederholt die Schleife mit einer neuen Auswertung der Bedingung.

Methoden-Definition

  def defname[( [arg[=val]]*[, *vararg][, &blockarg] )]body
  end

defname ist sowohl der Name der Methode als auch optional der Kontext, in dem dieser gültig ist.

defname <- methodname
expr.methodname

Ein Methoden-Name methodname ist entweder ein umdefinierbarer Operator (siehe Tabelle 18.4 auf Setie 221) oder ein Name. Falls methodname ein Name ist, sollte er mit einem Kleinbuchstaben (oder einem Underscore) beginnen, gefolgt von Groß- oder Kleinbuchstaben, Underscores und Ziffern. Ein Methoden-Name darf mit einem Fragezeichen (``?''), einem Ausrufezeichen (``!'') oder einem Gleichheitszeichen (``='') enden. Das Gleichheitszeichen ist zwar auch nur Teil des Namens, bedeutet aber zusätzlich, dass diese Methode ein Attribute-Setter ist (beschrieben auf Seite 25).

Eine Methodendefinition mit Hilfe eines simplen Methoden-Namens erzeugt man innerhalb einer Klasses oder eines Moduls eine Instanz-Methode. Eine Instanz-Methode kann man nur aktivieren, indem man ihren Namen an eine Instanz der definierenden Klasse schickt (oder einer der Sub-Klassen).

Außerhalb einer Klassen- oder Modul-Definition ergibt ergibt die Definition mit einem reinen Methoden-Namen eine private Methode der Klasse Object und kann daher in jedem Kontext ohne Angabe eines Empfängers aufgerufen werden.

Eine Definition über einen Methoden-Namen der Form expr.methodname erzeugt einer Methode, die an das durch expr gekennzeichnete Objekt gebunden ist; Diese Methode kann man nur über dieses Objekt aufrufen. In anderen Ruby-Dokumentationen werden diese Methoden Singleton-Methoden genannt.

class MyClass
  def MyClass.method      # Definition
  end
end

MyClass.method            # Aufruf

anObject = Object.new def anObject.method       # Definition end anObject.method           # Aufruf

def (1.class).fred        # Empfänger darf ein Ausdruck sein end Fixnum.fred               # Aufruf

In Methoden-Definitionen dürfen keine anderen Klassen-, Modul oder Instanz-Methoden-Definitionen vorkommen. Es dürfen aber verschachtelte Sigleton-Methoden-Definitionen enthalten sein. Der Rumpf einer Methode funktioniert wie ein begin/end-Block insofern, dass er Anweisungen zum Exception-Handling enthalten darf (rescue, else und ensure).

Methoden-Argumente

Eine Methoden-Definition darf null oder mehr reguläre Argumente enthalten, ein optionales Array-Argument und ein optionales Block-Argument. Die Argumente werden mit Kommas getrennt und dürfen in Klammern stehen.

Ein reguläres Argument ist ein Name einer lokalen Variable, optional gefolgt von einem Gleichheitszeichen und einem Defaultwert. Dieser Defaultwert wird ausgewertet, wenn die Methode aufgerufen wird. Ausdrücke werden von links nach rechts ausgewertet. Ein Ausdruck darf auf einen weiter vorn in der Parameterliste stehenden Parameter verweisen.

def options(a=99, b=a+1)
  [ a, b ]
end
options » [99, 100]
options 1 » [1, 2]
options 2, 4 » [2, 4]

Das optionale Array-Argument darf erst nach den regulärten Argumenten kommen und darf keinen Default-Wert besitzen.

Wenn die Methode aufgerufen wird, referenziert Ruby dieses Array-Argument auf ein neues Objekt der Klasse Array. Falls im Aufruf nach den regulären Argumenten noch weitere Argumente folgen, werden diese in diesem frisch erzeugten Array gesammelt.

def varargs(a, *b)
  [ a, b ]
end
varargs 1 » [1, []]
varargs 1, 2 » [1, [2]]
varargs 1, 2, 3 » [1, [2, 3]]

Erst wenn alle regulären Argumente (auch die mit Defaultwerten) gesetzt wurden, wird mit dem Rest das Array-Argument gefüllt.

def mixed(a, b=99, *c)
  [ a, b, c]
end
mixed 1 » [1, 99, []]
mixed 1, 2 » [1, 2, []]
mixed 1, 2, 3 » [1, 2, [3]]
mixed 1, 2, 3, 4 » [1, 2, [3, 4]]

Das optionale Block-Argument kommt erst ganz am Schluss. Bei jedem Aufruf der Methode schaut Ruby nach einem beigefügten Block. Falls einer vorhanden ist, wird er in ein Objekt der Klasse Proc konvertiert und diesem Block-Argument zugeordnet. Falls kein Block da ist, wird das Argument auf nil gesetzt.

Aufruf einer Methode

[receiver. ]name[parameters][block]
[receiver::]name[parameters][block]
parameters <- ( [param]*[, hashlist][*array][&aProc] )
block <- { blockbody }
do blockbody end

Die ersten Parameter werden den gegenwärtigen Argumenten der Methode zugewiesen. Danach darf eine Liste von Schlüssel => Werte-Paaren folgen. Diese Paare werden in einem einzelnen neuen Hash-Objekt zusammengefasst und der Methode als einzelner Parameter übergeben.

Danach darf ein einzelner Parameter mit einem vorangestellten Sternchen folgen. Wenn dieser Parameter ein Array ist, ersetzt Ruby damit ein oder mehrere Parameter.

def regular(a, b, *c)
  # ..
end
regular 1, 2, 3, 4
regular(1, 2, 3, 4)
regular(1, *[2, 3, 4])

Man kann einem Methoden-Aufruf einen Block mitgeben als literalen Block (der auf der selben bzw. letzten zum Methodenaufruf gehörenden Zeile anfangen muss) oder als Parameter mit vorangestelltem Ampersand-Zeichen, der auf ein Proc- oder Method-Objekt verweist. Egal ob ein Block-Argument vorhanden ist oder nicht sorgt Ruby dafür, dass die globale Funktion Kernel::block_given? die Verfügbarkeit eines mit dem Aufruf verbundenen Blocks anzeigt.

aProc   = proc { 99 } anArray = [ 98, 97, 96 ]

def block   yield end block { } block do       end block(&aProc)

def all(a, b, c, *d, &e)   puts "a = #{a}"   puts "b = #{b}"   puts "c = #{c}"   puts "d = #{d}"   puts "block = #{yield(e)}" end

all('test', 1 => 'cat', 2 => 'dog', *anArray, &aProc)
erzeugt:
a = test
b = {1=>"cat", 2=>"dog"}
c = 98
d = [97, 96]
block = 99

Eine Methode wird aufgerufen, indem man ihren Namen an einen Empfänger schickt. Wenn kein Empfänger angegeben ist, wird self vorausgesetzt.

Der Empfänger sucht in seiner eigenen Klasse nach der Methoden-Definition und dann nacheinander in seinen Vorgänger-Klassen. Die Instanz-Methoden von eingebundenen Modulen funktionieren dabei, als wären sie in einer anonymen Über-Klasse der Klasse, in der sie eingebunden werden. Wenn die Methode nicht gefunden wird, ruft Ruby die Methode method_missing des Empfängers auf. Das Standard-Verhalten, definiert in Kernel::method_missing, ist dann die Meldung eines Fehlers gefolgt vom Programmabbruch.

Wenn ein Empfänger explizit bei einem Methodenaufruf angegeben ist, darf er vom Methoden-Namen entweder durch einen Punkt ``.'' oder zwei Doppelpunkte ``::'' getrennt sein. Der einzige Unterschied zwischen diesen beiden Formen fällt auf, wenn der Methoden-Name mit einem Großbuchstaben anfängt. In diesem Fall nimmt Ruby an, dass ein receiver::Thing-Methoden-Aufruf tatsächlich der Versuch ist, auf eine Konstante mit Namen Thing zuzugreifen außer es gibt noch eine geklammerte Parameterliste nach dem Methoden-Namen.

Foo.Bar()         #  Methoden-Aufruf
Foo.Bar           #  Methoden-Aufruf
Foo::Bar()        #  Methoden-Aufruf
Foo::Bar          #  Zugriff auf Konstante

Der Rückgabewert einer Methode ist der Wert des letzten ausgewerteten Ausdrucks.

return [expr]*

Ein return-Ausdruck beendet die Methode sofort. Der Wert von return ist nil, wenn kein Parameter angegeben wurde, der Wert dieses Parameters, wenn einer angegeben wurde, oder ein Array mit allen Parametern, wenn mehrere angegeben wurden.

super

super  [ ( [param]*[*array] ) ][block]

Innerhalb des Rumpfes einer Methode funktioniert der Aufruf von super so, als wäre die original-Methode aufgerufen worden, Allerdings startet die Suche nach dem Methoden-Rumpf in der Über-Klasse des Objekts, das die Original-Methode enthält. Falls keine Parameter (und auch keine Klammern) angegeben wurden, bekommt dieser super-Aufruf die selben Parameter wie die Original-Methode mit, ansonsten die angegebenen.

Operator-Methoden

expr1 operator

operator expr1

expr1 operator expr2

Wenn der Operator in einem Operator-Ausdruck zu einer umdefinierbaren Methode (siehe Tabelle 18.4 auf Seite 221) gehört, führt Ruby ihn aus wie

(expr1).operator(expr2)

Attribut-Zuweisung

receiver.attrname = rvalue

Wenn die Form receiver.attrname als Lvalue auftaucht, ruft Ruby eine Methode mit Namen attrname= im Empfänger receiver auf und übergibt rvalue als einzelnen Parameter.

Element-Referenz-Operator

receiver [expr]+

receiver [expr]+ = rvalue

Als Rvalue ruft die Element-Referenz die Methode [] des Empfängers auf und übergibt ihm als Parameter die Ausdrücke in Klammern.

Als Lvalue ruft die Element-Referenz die Methode []= des Empfängers auf und übergibt ihm als Parameter die Ausdrücke in Klammern, gefolgt von dem zugehörigen rvalue.

Aliasing

alias neuerNamealterName

erzeugt einen neuen Namen, der auf eine schon existierende Methode, Operator, globale Variable, Klassen-Variable oder Rückverweis eines regulären Ausdrucks ($&, $', $' und $+) verweist. Lokale Variablen, Instanz-Variablen, Klassen-Variablen und Konstanten dürfen nicht mit Alias neu verwiesen werden. Die Parameter für alias dürfen Namen oder Symbole sein.

class Fixnum
  alias plus +
end
1.plus(3) » 4
alias $prematch $`
"string" =~ /i/ » 3
$prematch » "str"
alias :cmd :`
cmd "date" » "Sun Mar  4 23:24:32 CST 2001\n"

Wenn eine Methode mit alias neu verwiesen wird, so weist der neue Name auf eine Kopie des Methodenrumpfs. Falls die Methode später umdefiniert wird, ruft der Alias-Name immer noch die Original-Fassung auf.

def meth
  "original method"
end
alias original meth
def meth
  "new and improved"
end
meth » "new and improved"
original » "original method"

Klassen-Definition

class classname[< superexpr]body
end

class << anObjectbody end

Eine Ruby-Klassen-Definition erzeugt oder erweitert ein Objekt der Klasse Class durch Ausführung des Codes in body. Bei der ersten Form wird eine benamte Klasse erzeugt oder erweitert. Das resultierende Class-Objekt wird einer globalen Konstante namens classname zugewiesen. Der Name sollte mit einem Großbuchstaben anfangen. Bei der zweiten Form wird eine anonyme (Singleton-) Klasse diesem speziellen Objekt zugewiesen.

Falls vorhanden so sollte superexpr ein Ausdruck sein, der auf ein Class-Objekt verweist, das dann als Superklasse der definierten Klasse dient. Falls er fehlt, wird standardmäßig die Klasse Object verwendet.

Innerhalb von body werden die meisten Ruby-Ausdrücke einfach so ausgewertet, wie sie da stehen. Allerdings:

Wir müsssen anmerken, dass eine Klassen-Definition ausführbarer Code ist. Viele der in einer Klassen-Definition benutzen Anweisungen (wie etwa attr oder include) sind tatsächlich einfache private Instanz-Methoden der Klasse Module (Dokumentiert ab Seite 348).

Kapitel 19 ab Seite 241 beschreibt genauer, wie Class-Objekte mit dem Rest der Umgebung interagieren.

Objekt-Erzeugung durch Klassen

obj = classexpr.new [ ( [args]* ) ]

Die Klasse Class definiert die Instanz-Methode Class#new, und

Wenn eine Klassen-Definition die Klassen-Methode new überschreibt ohne dabei super aufzurufen, so werden keine Objekte dieser Klasse erzeugt.

Deklaration von Klassen-Attributen

Deklarationen von Klassen-Attributen sind technisch gesehen gar nicht Teil der Sprache Ruby: es sind einfach nur Methoden aus der Klasse Module die automatisch Zugriffs-Methoden erzeugen.

class name
  attr attribute[, writable]
  attr_reader     [attribute]+
  attr_writer     [attribute]+
  attr_accessor   [attribute]+
end

Modul-Definitionen

module namebody
end

Im Grunde ist ein Modul eine Klasse, die nicht instanziiert werden kann. Wie bei einer Klasse wird der Rumpf während der Definition ausgeführt und das sich ergebende Module-Objekt wird in einer Konstanten gespeichert. Ein Modul darf Klassen- und Instanz-Methoden enthalten und Konstanten und Klassen-Variablen definieren. Wie bei Klassen werden Modul-Methoden über das Module-Objekt als Empfänger aufgerufen und auf Konstanten wird über den Bereichs-Operator ``::'' zugegriffen.

module Mod
  CONST = 1
  def Mod.method1    # module method
    CONST + 1
  end
end

Mod::CONST » 1
Mod.method1 » 2

Mixins --- Module einbinden

class|module name
  include expr
end

Mit der include-Methode kann man ein Modul innerhalb der Definition eines andern Moduls oder einer anderen Klasse einbinden. Die Modul- oder Klassen-Definition mit einem solchen include erhält Zugriff auf die Konstanten, Klassen-Variablen und Instanz-Methoden des eingebundenen Moduls.

Wenn ein Modul innerhalb einer Klassen-Definition eingebunden wird, so werden die Konstanten, Klassen-Variablen und Instanz-Methoden dieses Moduls gewissermaßen in einer anonymen (und unzugänglichen) Super-Klasse dieser Klasse zusammengefasst. Im Speziellen werden Objekte dieser Klasse auf Nachrichten für die Instanz-Methoden dieses Moduls reagieren.

Ein Modul kann auch auf dem Top-Level eingebunden werden, in diesem Fall werden die Konstanten, Klassen-Variablen und Instanz-Methoden auf dem Top-Level verfügbar.

Modul-Funktionen

Obwohl include ganz nützlich ist, um Mixin-Funktionalität zur Verfügung zu stellen, bietet es auch die Möglichkeit, Konstanten, Klassen-Variablen und Instanz-Methoden in einen anderen Namensraum zu verfrachten. Allerdings ist die in einer Instanz-Methode definierte Funktionalität nicht als Modul-Methode verfügbar.

module Math
  def sin(x)
    #
  end
end

# Zugriff auf Math.sin nur über ... include Math sin(1)

Die Methode Module#module_function löst dieses Problem, indem sie eine oder mehrere Instanz-Methoden eines Moduls nimmt und ihre Definitionen in korrespondierende Modul-Methoden kopiert.

module Math
  def sin(x)
    #
  end
  module_function :sin
end

Math.sin(1) include Math sin(1)

Die Instanz-Methode und die Modul-Methode sind zwei verschiedene Methoden: die Methoden-Definition wird von module_function kopiert, es wird keinesfalls ein einfacher alias-Verweis erzeugt.

Zugriffs-Kontrolle

Ruby definiert drei Level des Zugriffsschutzes für Konstanten und Methoden von Modulen oder Klassen:

private    [aSymbol]*
protected  [aSymbol]*
public     [aSymbol]*

Jede Funktion kann auf zwei Arten benutzt werden.
  1. Ohne Argumente setzen die drei Funktionen die Standard-Zugriffs-Kontrolle für nachfolgend definierte Methoden.
  2. Mit Argumenten setzen die Funktionen die Zugriffs-Kontrolle für die angegebenen Methoden und Konstanten.

Die Zugriffs-Kontrolle wird bei Aufruf einer Methode abgeprüft.

Blöcke, Closures und Proc-Objekte

Ein Code-Block ist eine Ansammlung von Ruby-Anweisungen und -Ausdrücken zwischen geschweiften Klammern oder einem do/end-Paar. Der Block darf mit einer Argumentliste zwischen senkrechten Strichen beginnen. Ein Code-Block darf nur unmittelbar nach einem Methoden-Aufruf auftauchen. Der Anfang des Blocks muss auf der selben logischen Zeile beginnen auf der der Methoden-Aufruf aufhört.

invocation  do  | a1, a2, ... |
end

invocation { | a1, a2, ... | }

Geschweifte Klammern besitzen hohe Priorität; do besizt niedrige Priorität. Falls der Methoden-Aufruf Parameter besitzt, die nicht in Klammern eingeschlossen sind, so gehört der Block mit den geschweiften Klammern zum letzten dieser Parameter, nicht zu dem Aufruf. Der Block mit do gehört dagegen zum Aufruf.

Innerhalb des Rumpfes der aufgerufenen Methode kann der Code-Block mittels der yield-Methode aufgerufen werden. Wenn man an yield Parameter gibt, so werden sie an den Block weitergegeben nach den Regeln der parallelen Zuweisung, beschreiben ab Seite 222. Der Rückgabewert von yield ist der Wert des letzten innerhalb des Blocks ausgewerteten Ausdrucks.

Ein Code-Block merkt sich die Umgebung, in der er definiert wurde, und er benutzt bei jedem Aufruf diese Umgebung.

Proc-Objekte

Code-Blöcke werden durch die Methoden Proc.new und Kernel#proc in Objekte der Klasse Proc konvertiert oder, indem man sie einer Methode als Block-Argument mitgibt.

Der Proc-Konstruktor nimmt einen zugeordneten Block und verpackt ihn zusammen mit genügend Kontext, um dessen Umgebung wieder erstellen zu können, wenn der Block später aufgerufen wird. Mit der Proc#call-Instanz-Methode kann man den ursprünglichen Block aufrufen und optional noch Parameter mitgeben. Der Code im Block (und im dazu gehörenden Closure) bleibt während der gesamten Lebensspanne des Proc-Objekts verfügbar.

Wenn das letzte Argument in der Argument-Liste einer Methode mit einem Ampersand (``&'') anfängt, so wird jeder Block, der mit der Methode verbunden ist, in ein Proc-Objekt konvertiert und diesem Parameter zugewiesen.

Exceptions

Ruby-Exceptions sind Objekte der Klasse Exception und seiner Nachfahren (eine komplette Liste der eingebauten Exceptions liefert Figur 22.1 auf Seite 303).

Exceptions auslösen

Die Kernel::raise-Methode löst eine Exception aus.

raise
raise aString
raise thing[, aString[aStackTrace]]

Die erste Form wiederholt die Auslösung der Exception in $! oder löst einen RuntimeError aus, wenn $! nil ist. Die zweite Form erzeugt eine neue RuntimeError-Exception und setzt deren Meldung auf den mitgegebenen String. Die dritte Form löst eine Exception aus, indem sie die Methode exception ihres ersten Arguments ausführt. Sie setzt dann die Meldung dieser Exception und zieht sich auf das zweite und dritte Argument zurück. Die Klasse Exception und Objekte der Klasse Exception enthalten eingebaute Methoden mit Namen exception, so dass man den Namen oder die Instanz einer Exception-Klasse als ersten Parameter für raise benutzen kann.

Wenn eine Exception ausgelöst wird, setzt Ruby eine Referenz auf das Exception-Objekt in die globale Variable $!.

Exception-Behandlung

Exceptions kann man im Rahmen eines begin/end-Blocks abhandeln.

  begin
    code...code...[rescue  [parm]*[=> var][then]error handling code...]*[else
    no exception code...][ensure
    always executed code...]
  end

Ein Block darf mehrere rescue-Klauseln besitzen und jede davon darf null oder mehrere Parameter haben. Eine rescue-Klausel ohne Parameter wird behandelt, als hätte sie den Parameter StandardError.

Wenn eine Exception ausgelöst wird, hangelt sich Ruby den Aufruf-Stack entlang, bis es einen umschließenden begin/end-Block findet. Ruby vergleicht dann bei jeder rescue-Klausel in diesem Block deren Parameter mit der ausgelösten Exception; jeder Parameter wird mit $!.kind_of?(parameter) abgeprüft. Wenn die ausgelöste Exception auf einen rescue-Parameter passt, führt Ruby den Rumpf dieses rescue aus und sucht danach nicht mehr weiter. Wenn die passende rescue-Klausel mit einem => und einem Variablen-Namen aufhört, wrid die Variable auf $! gesetzt.

Obwohl die Parameter für die rescue-Klauseln üblicherweise Namen von Exception-Klassen sind, können sie tatsächlich alle möglichen Ausdrücke sein (auch Methoden-Aufrufe), die eine passende Klasse zurückliefern.

Falls keine rescue-Klausel auf die augelöste Exception passt, hangelt sich Ruby am Stack-Frame weiter und sucht nach einem passenden höher-leveligen begin/end-Block. Falls das bis zum Top-Level so weitergeht, ohne dass ein passendes rescue gefunden wurde, wird das Programm mit einer Fehlermeldung beendet.

Wenn eine else-Klausel vorhanden ist, wird ihr Rumpf ausgeführt, falls keine Exception im ursprünglichen Code ausgelöst wurde. Exceptions, die während der Ausführung der else-Klausel auftreten, werden nicht von der rescue-Klausel aus dem selben Block wie else abgefangen.

Wenn eine ensure-Klausel vorhanden ist, wird ihr Rumpf immer ausgeführt, bevor der Block verlassen wird (auch falls eine nicht abgefangene Exception danach weitergereicht wird).

Block-Wiederholung, Retry

Die retry-Anweisung kann man innerhalb einer rescue-Klausel benutzen, um den umschließenden begin/end von Anfang an zu wiederholen.

Catch und Throw

Die Methode Kernel::catch führt den dazugehörenden Block aus.

catch ( aSymbol | aString )  do
  block...
end

Dei Methode Kernel::throw unterbricht den normalen Ablauf der Anweisungen.

throw( aSymbol | aString[, anObject] )

Wenn ein throw ausgeführt wird, sucht Ruby den Aufruf-Stack nach dem ersten catch-Block mit passendem Symbol oder String ab. Wenn es einen findet, wird die Suche beendet und die Ausführung wird nach dem Beenden des catch-Blocks wiederaufgenommen. Ruby beachtet dabei die ensure-Klauseln aller Block-Ausdrücke, die es bei der Suche nach einem passenden catch durchläuft.

Falls kein passender catch-Block gefunden wird, löst Ruby eine NameError-Exception an der Stelle des throw aus.


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".