Programmierung 3 (PR3) - Ruby Vorlesung - Hochschule Mannheim
Ruby Grundlagen
Prof. Thomas Smits
Wintersemester 2021/2022
9. November 2021
Diese Materialien sind ausschließlich für den persönlichen Gebrauch durch Besucher der Vorlesung Programmierung 3 (PR3) an der Hochschule Mannheim gedacht. Jede andere Nutzung ist ohne vorherige Zustimmung des Autors nicht zulässig.
Inhaltsverzeichnis
1 Einführung 1
1.1 Was ist Ruby? [5] . . . 1
1.2 Eigenschaften [6] . . . 2
1.3 Interactive Ruby Shell [7] . . . 3
1.4 Schlüsselworte [8] . . . 4
1.5 Klammern in Methoden [9] . . . 4
1.6 Strings [10]. . . 5
1.7 Formatter [11] . . . 6
1.8 Alles is ein Objekt [12] . . . 7
1.9 nil ist ein Objekt [13] . . . 8
1.10 Symbole [14] . . . 9
1.11 Symbol↔String [15] . . . 10
1.12 Ranges [16]. . . 11
1.13 Parallele Zuweisung [18] . . . 12
1.14 Bedingte Zuweisung [20] . . . 13
1.15 Kommentare [21] . . . 14
1.16 Namenskonventionen [22] . . . 14
1.17 Methodennamen [23] . . . 15
1.18 Zeilenorientierung [24] . . . 16
1.19 BEGIN und END-Blöcke [25] . . . 17
2 Kontrollstrukturen 19 2.1 Überblick [27] . . . 19
2.2 if [28] . . . 19
2.3 unless [31] . . . 21
2.4 Was ist true? [32] . . . 21
2.5 Double Bang Operator [33] . . . 22
2.6 Case [34] . . . 23
2.7 Case und Ranges [37] . . . 25
3 Schleifen 27 3.1 Kopfgesteuerten while-Schleife [39] . . . 27
3.2 while als Modifier [40] . . . 28
Seite ii
Inhaltsverzeichnis Inhaltsverzeichnis
3.3 Fußgesteuerte while-Schleife [41] . . . 28
3.4 until-Schleife [42] . . . 29
3.5 for-Schleife [43] . . . 30
3.6 Kontrolle der Schleife [45] . . . 31
3.7 Scope der Variablen [49] . . . 32
4 Ausdrücke 34 4.1 Alles hat einen Rückgabewert [51]. . . 34
4.2 Operatoren als Methoden [52] . . . 34
4.3 Beispiel: Operatoren implementieren [53] . . . 35
4.4 Betriebssystem-Kommandos [55] . . . 36
4.5 Zuweisungen [56] . . . 37
4.6 Parallele Zuweisung [57] . . . 38
4.7 Splat [58] . . . 39
4.8 Bedingte Ausführung [59] . . . 40
4.9 defined? [60] . . . 41
4.10 Vergleichsoperatoren [61] . . . 42
5 Eingabe 45 5.1 Einlesen von der Konsole [63] . . . 45
5.2 Umwandlung der Daten [65] . . . 46
6 Organisation der Quelltexte 47 6.1 Dateinamen [67] . . . 47
6.2 Datei-Struktur [68] . . . 47
6.3 Dateien laden [70] . . . 48
6.4 Gems [72] . . . 49
7 Unit-Tests 51 7.1 Frameworks [74]. . . 51
7.2 Minitest Assertions [75]. . . 51
7.3 Beispiel: Minitest [76] . . . 52
7.4 Spec-Style [77] . . . 53
8 Ruby-Dokumentation 54 8.1 Werkzeuge zur Dokumentation [79] . . . 54
8.2 ri [80] . . . 54
8.3 Methoden beschreiben [81] . . . 55
8.4 Auszeichnungen [82] . . . 56
8.5 Yard [84] . . . 56
8.6 Yard-Tags [86] . . . 57
Kapitel 1
Einführung
1.1 Was ist Ruby? [5]
■ Entwickelt seit 1993 von Yukihiro Matsumoto (Matz) in Japan
■ Erste Veröffentlichung 1995
⇒genauso alt wie Java
■ Extrem populär für Webentwicklung wegenRuby on Rails
Yukihiro Matsumoto, Quelle: Wikipedia
Ruby ist als Sprache genauso alt wie Java, hat aber deutlich andere Konzepte und Wurzeln. Yukihiro Matsumoto hat für Ruby die Konzepte der Programmiersprachen Perl, Smalltalk, Eiffel, Ada und Lisp kombiniert und versucht eine Sprache zu entwickeln, die sowohl funktionale als auch imperative Programmierung erlaubt.
I am trying to make Ruby natural, not simple.
Yukihiro Matsumoto
Mit der Entwicklung des WebframeworksRuby on Railswurde Ruby extrem populär und sie ist heute imTIOBE-Indexnoch immer unter den populärsten Programmiersprachen (Stand Januar 2021 auf Platz 15).
Seite 1
1 Einführung 1.2 Eigenschaften [6]
1.2 Eigenschaften [6]
Ruby hat einige zentrale Eigenschaften, die da sind:
■ Skriptsprache (⇒kein Compiler)
■ multiparadigmatisch
▶ objektorientiert (Klassen oder Prototypen)
▶ prozedural
▶ funktional
▶ Metaprogrammierung
▶ aspektorientierte Programmierung
■ alles ist ein Objekt
■ dynamische Typisierung
■ plattformunabhängig
Bei Ruby handelt es sich um eine Skriptsprache, die – anders als Java – keinen Compiler benö- tigt, sondern durch einem Interpreter ausgeführt wird. Der Interpreter ist mit einer Java VM zu vergleichen, allerdings verarbeitet er direkt den Quelltext und keinen Bytecode wie Java.
Eine Besonderheit von Ruby ist, dass die Sprache nicht streng einem Paradigma folgt. Anders als Java, das rein objektorientiert ist oder Lisp, das rein funktional ist, unterstützt Ruby mehrere Programmierparadigmen.
■ objektorientiert: In Ruby kann man Klassen definieren und Objekte manipulieren. Ruby geht sogar so weit, dass alles in Ruby ein Objekt ist, d. h. es gibt z. B. keine primitiven Datentypen.
■ prozedural: Man muss in Ruby nicht mit Klassen und Objekten arbeiten, sondern man kann wie in C ausschließlich mit Funktionen und Prozeduren programmieren. Hierdurch kann man Skripte schreiben, die nur ein paar Zeilen enthalten.
■ funktional: In Ruby sind Funktionen eigenständige Entitäten, die man – wie in der funktiona- len Programmierung üblich – analog zu Objekten manipulieren und verarbeiten kann.
■ Metaprogrammierung: Ruby erlaubt es, dass Programme sich selbst oder andere Programme verändern können.
■ aspektorientierte Programmierung: Es ist in Ruby möglich verschiedene Aspekte des Pro- gramms (z. B. Logging und normale Logik) voneinander zu trennen und als sogenannte Aspekte zu behandeln.
Wie bei Skriptsprachen üblich, müssen Variablen in Ruby nicht mit einem Typ deklariert werden, sondern entstehen bei der ersten Benutzung und können zu verschiedenen Zeiten Daten unter- schiedlichen Typs enthalten. Die von den Variablen referenzierten Objekte (alles ist in Ruby ein Objekt) kennen ihren Typ, den Variablen ist dieser aber egal, sie zeigen einfach auf alles.
1 Einführung 1.3 Interactive Ruby Shell [7]
Variablen in Ruby
a = 5 a = 'Hallo'
Variablen in Java
int a = 5;
String b = "Hallo";
Ruby ist eine plattformunabhängige Sprache, weil Ruby-Programme auf allen Systemen ausgeführt werden können, für die es einen Ruby-Interpreter gibt. Da ein in Java geschriebenen Ruby-Interpreter (JRuby) existiert, kann man Ruby auf allen Plattformen ausführen, für die eine Java-VM verfügbar ist.
1.3 Interactive Ruby Shell [7]
■ Interactive Ruby Shell(irb)
→Programme direkt interaktiv ausprobieren
■ REPL= Read Evaluate Print Loop
■ Start mit dem Kommandoirb
~$ irb
irb( main ):001:0 > puts " Hello World "
Hello World
=> nil
irb(main ):002:0 > 2+2*2
=> 6
irb(main ):003:0 > exit
~$
Mit derInteractive Ruby Shell(irb) kann man Ruby Befehle interaktiv ausprobieren. Jeder einge- gebene Befehl wird sofort evaluiert und der Rückgabewert wird ausgegeben. Anders als in einem
„echten“ Programm muss man beiirbdie Ergebnisse nicht extra mitprintoderputsausgeben.
Will man ein Ruby-Programm ausführen, das als Datei vorliegt, verwendet man anstatt der irb den Ruby-Interpreterruby. Ein beispielhafter Aufruf sieht damit so aus:ruby mein_skript.rb. Unter Unix kann man ein Ruby-Skript direkt als ausführbar markieren (chmod a+x DATEINAME).
Dazu muss die erste Zeile des Skripts den Pfad zum Ruby-Interpreter enthalten, z. B.
#!/usr/bin/ruby puts 2+2
Ruby Grundlagen Seite 3
1 Einführung 1.4 Schlüsselworte [8]
Den speziellen Ausdruck#!bezeichnet man alsShebang). Er muss in der ersten Zeile des Skriptes stehen, um zu funktionieren.
1.4 Schlüsselworte [8]
__ENCODING__ __FILE__ __LINE__ alias and
begin BEGIN break case class
def defined ? do else elsif
end END ensure false for
if in module next nil
not or redo rescue retry
return self super then true
undef unless until when while
yield
Ruby hat weniger Schlüsselworte als Java, das ca. 50 besitzt.
Java Schlüsselworte
abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while
Vieles, das auf den ersten Blick wie ein Schlüsselwort aussieht, z. B. Schleifen mitloop, sind in Wirklichkeit Funktionen, die durch Rubys flexiblen Syntax nicht als solche erscheinen.
1.5 Klammern in Methoden [9]
Klammern bei Methodenaufrufen können weggelassen werden
> "Hallo".size
=> 5
> "Hallo".size()
=> 5
1 Einführung 1.6 Strings [10]
> "Hallo Thomas".split(' ')
=> ["Hallo", "Thomas"]
> "Hallo Thomas".split ' '
=> ["Hallo", "Thomas"]
Dem Prinzip folgend, dass Ruby möglichst natürlich sein soll, sind z. B. die Klammern bei Methoden- aufrufen optional. Bei der Deklaration von Methoden kann man ebenfalls die Klammern weglassen, wobei die Konvention bei den meisten Ruby-Programmen wie folgt aussieht:
■ Keine Klammern
▶ Deklaration einer Methode ohne Parameter:def m
▶ Aufruf einer Methode ohne Parameter:m
■ Mit Klammern
▶ Deklaration einer Methode mit Parametern:def m(a, b)
▶ Aufruf einer Methode mit Parametern:m(1, 2)
1.6 Strings [10]
Begrenzer(Delimiter) für Strings sind'oder"
> puts "Hallo"
Hallo
> puts 'Hallo' Hallo
Variablen können innerhalb von"-Strings über#{VAR}eingesetzt werden
> a = 77
> puts "A ist #{a}"
A ist 77
> puts 'A ist #{a}' A ist #{a}
Anders als in Java, wird in Ruby nicht zwischenStringundCharacterunterschieden, d. h. selbst ein einzelnes Zeichen ist ein String. Trotzdem gibt es zwei verschiedene Arten von String-Begrenzern 'und", die sich darin unterscheiden, wie Variablen undEscape-Sequenzenbehandelt werden.
Strings, die mit'begrenzt werden:
■ Variablen#{VAR}und andere Ruby-Ausdrücke#{AUSDRUCK}werdennichtersetzt
Ruby Grundlagen Seite 5
1 Einführung 1.7 Formatter [11]
■ Escape-Sequenzen (\) werden nicht interpretiert, außer\' Strings, die mit"begrenzt werden:
■ Variablen#{VAR}und andere Ruby-Ausdrücke#{AUSDRUCK}werden durch ihren Wert ersetzt
■ Es gibt eine ganze Reihe von Escape-Sequenzen, die interpretiert werden, z. B.
▶ \": Das Anführungszeichen selbst
▶ \n: newline→neue Zeile
▶ \t: tab→Tabulator
▶ \b: backspace→vorheriges Zeichen löschen
▶ . . .
Ein weiterer Vorteil der beiden unterschiedlichen String-Begrenzer ist, dass man den jeweils anderen problemlos in den String einschließen kann.
puts '"Oh, je" sagte die Mutter' puts "Lukas' Idee war grandios"
1.7 Formatter [11]
Ähnlich zuSystem.out.printfin Java:
> puts "Der Preis für %s ist %.2f EUR" % [ "Kuchen", 42.2322222 ]
> sprintf("Der Preis für %s ist %.2f EUR", "Kuchen", 42.2322222)
Der Preis für Kuchen ist 42.23 EUR Der Preis für Kuchen ist 42.23 EUR
Benannte Platzhalter
puts "The %{food} is a %{attribute}" % { food: 'cake', attribute: 'lie' }
The cake is a lie
Die String-Formatierung mit demFormatterfunktioniert analog zur Java-MethodeString.format und verwendet dieselben Platzhalter und Möglichkeiten zur Längenangabe. Sowohl Java als auch
1 Einführung 1.8 Alles is ein Objekt [12]
Ruby übernehmen diese aus der Funktionsprintfder C-Standardbibliothek. Die Dokumentation zur Funktionsweise der Platzhalter findet sich bei Ruby in der Klasse Kernel in der Methode sprintf.
Das%zwischen dem String und den Daten, die angezeigt werden sollen ist ein überladener Modulo- Operator (%).
Beispiele für Formatierungen
# `+' and space flag specifies the sign of non-negative numbers.
sprintf("%d", 123) #=> "123"
sprintf("%+d", 123) #=> "+123"
sprintf("% d", 123) #=> " 123"
# padding is done by spaces, width=20
# 0 or radix-1. <--->
sprintf("%20d", 123) #=> " 123"
sprintf("%+20d", 123) #=> " +123"
sprintf("%020d", 123) #=> "00000000000000000123"
sprintf("%+020d", 123) #=> "+0000000000000000123"
sprintf("% 020d", 123) #=> " 0000000000000000123"
# precision for `f' is number of
# digits after the decimal point <--->
sprintf("%20.8f", 1234.56789) #=> " 1234.56789000"
# precision for `s' is
# maximum number of characters <--->
sprintf("%20.8s", "string test") #=> " string t"
sprintf("%d %04x", 123, 123) #=> "123 007b"
sprintf("%08b '%4s'", 123, 123) #=> "01111011 ' 123'"
sprintf("%1$*2$s %2$d %1$s", "hello", 8) #=> " hello 8 hello"
sprintf("%1$*2$s %2$d", "hello", -8) #=> "hello -8"
sprintf("%+g:% g:%-g", 1.23, 1.23, 1.23) #=> "+1.23: 1.23:1.23"
sprintf("%u", -123) #=> "-123"
1.8 Alles is ein Objekt [12]
■ Alles in Ruby ist ein Objekt
■ Keine primitiven Datentypen
> -2.abs
=> 2
> -2.positive?
=> false
Ruby Grundlagen Seite 7
1 Einführung 1.9 nil ist ein Objekt [13]
> 2.positive?
=> true
Anders als Java, unterscheidet Ruby nicht zwischen primitiven Datentypen und Objekten. (Fast) alles in Ruby ist ein Objekt, Ganzzahlen, Fließkommazahlen etc. Als Konsequenz hieraus, kann man auf allen „Daten“ in Ruby Methoden aufrufen.
Die einzige Ausnahme in Ruby sind Blöcke, Methoden und Funktionen, die keine Objekte sind. Sie lassen sich aber überProcsundLambdasjederzeit in Objekte umwandeln, sodass im Endeffekt alles als Objekt manipuliert und betrachtet werden kann.
Block als Objekt
doppler = Proc.new { |a| 2*a } doppler.call(5) # => 10
Funktion/Methode als Objekt
# Funktion (kein Objekt) def quadrierer(a)
a*a end
# Objekt zur Funktion beschaffen m = method(:quadrierer)
m.call(5) # => 25
1.9 nil ist ein Objekt [13]
■ Java:null⇒kein Objekt, sondern Referenz ins Nichts
■ Ruby:nil⇒ein Objekt vonNilClass
Java-Beispiel
String a = null;
if (a != null) {
System.out.println(a.length());
}
Ruby-Beispiel
a = nil
if !a.nil? then puts a.size end
1 Einführung 1.10 Symbole [14]
Im Gegensatz zu Java muss mannilnicht gesondert behandeln, sondern kann es wie jedes andere Objekt verarbeiten. Eine Besonderheit vonnilist, dass es automatisch in Kontrollstrukturen als falseevaluiert wird. Damit kann man sich den Test mitnil?so gut wie immer sparen, da die Variable selbst ausgewertet werden kann und bei einem Wert vonnilalsfalsegilt.
a = nil if a then
puts a.size end
1.10 Symbole [14]
Symbole(Symbols)
■ Konstante Namen
■ Müssen nicht vordeklariert werden
■ Singletons
def walk(direction) if direction == :north
# ...
end end
walk(:east) walk(:west)
Symbole sind eine Besonderheit von Ruby und führen oft zu Irritationen. Symbole erfüllen ungefähr die Aufgabe, die Enumerationen in Java haben: Die Definition von festen, unveränderlichen Werten, die für die Übergabe an Methoden benutzt werden können. Außerdem werden Symbole überall sonst eingesetzt, wo man konstante Werte benötigt, z. B. um Methoden zu benennen, um Keys in Hashes (⇔Mapin Java) zu definieren etc.
Enumeration in Java
public enum Planet {
MERKUR, VENUS, ERDE, MARS, JUPITER,SATURN, URANUS, NEPTUN;
// PLUTO;
}
public class SpaceTravel {
private static final Planet HOME_PLANET = Planet.ERDE;
Ruby Grundlagen Seite 9
1 Einführung 1.11 Symbol↔String [15]
public void travel(Planet zielPlanet) { if (zielPlanet == HOME_PLANET) {
return; // wir sind doch schon da }
switch (zielPlanet) {
case JUPITER: // langer Flug break;
case MARS: // kurzer Flug break;
// ...
} } }
Analoges Programm in Ruby
HOME = :erde def travel(ziel)
return 'schon da' if ziel == HOME case ziel
when :jupiter 'langer Flug' when :mars
'kurzer flug'
# ...
endend
Strings sind in Ruby veränderbar, Symbole nicht. Deswegen können sie alsSingletonsrealisiert werden und lösen das Problem, dass der Schlüssel in einer Hashtabelle (Hash in Ruby) nachträglich nicht verändert werden darf. Generell sollte man immer dann Symbole bevorzugen, wenn man nicht den Inhalt des Strings benötigt, sondern er nur als Platzhalter für einen möglichen Wert dient.
1.11 Symbol↔String [15]
■ id2name,to_s: Symbol→String
■ intern,to_sym: String→Symbol :my_sym.id2name # => "my_sym"
1 Einführung 1.12 Ranges [16]
:my_sym.to_s # => "my_sym"
"string_sym".intern # => :string_sym
"string_sym".to_sym # => :string_sym
Häufig möchte man aus einem String ein Symbol erzeugen oder umgekehrt ein Symbol in einen String umwandeln. Hierzu dienen die Methodento_saufSymbolbzw.to_symaufString.
1.12 Ranges [16]
■ Rangessind ein eigener Datentyp in Ruby
■ Beschreiben einen Bereich von Werten (Intervall mit Beginn und Ende)
▶ a..b: Vonabisbinklusiveb
▶ a...b: Vonabisbexklusiveb print (7..2).to_a # => []
print (1..5).to_a # => [1, 2, 3, 4, 5]
print (1...5).to_a # => [1, 2, 3, 4]
print (-5..-1).to_a # => [-5, -4, -3, -2, -1]
print ('a'..'c').to_a # => ["a", "b", "c"]
print ('a'...'c').to_a # => ["a", "b"]
Ein weiterer Datentyp, der relativ spezifisch für Ruby ist, sind dieRanges. Ein Range beschreibt einenWertebereich, der über sein Beginn und Ende angegeben werden kann. Der verwendete Datentyp muss eine natürliche Ordnung haben, damit man feststellen kann, ob ein Wert innerhalb oder außerhalb des Ranges liegt, z. B. Integer, Float, String etc. Eigene Datentypen müssen die
<=>-Methode implementieren (analog zucompareToin Java), um in einem Range verwendet werden zu können.
Wenn der Datentyp zusätzlich noch eine endliche Anzahl von Werten zwischen den Grenzen hat, kann man sich durch dieto_a-Methode alle Elemente als Array ausgeben lassen.Achtung:Dies kann zu einem erheblichen Speicherverbrauch führen.
Der Vorteil von Ranges ist, dass sie
■ speichersparend sind: Anstatt alle Werte zu speichern, muss man nur die Grenzen ablegen;
■ außerdem kann man mitinclude?leicht prüfen, ob ein Wert im Range liegt.
Ranges werden später noch ausgiebiger behandelt.
Beispiel: Ranges
print ('aa'..'bb').to_a
Ruby Grundlagen Seite 11
1 Einführung 1.13 Parallele Zuweisung [18]
Ausgabe
[" aa", "ab", "ac", "ad", "ae", "af", "ag", "ah", "ai", "aj", "ak", "al",
"am", "an", "ao", "ap", "aq", "ar", "as", "at", "au", "av", "aw", "ax",
"ay", "az", "ba", "bb "]
Während der Range'aa'..'bb'nur den Speicher für seine beiden Grenzen verbraucht, wird bei der Umwandlung in einen String mitto_afür jedes Element zwischen diesen beiden Grenzen entsprechend Hauptspeicher benötigt. Aus diesem Grund sollte man Ranges nur dann in Arrays umwandeln, wenn dies auch wirklich nötig ist.
1.13 Parallele Zuweisung [18]
Parallele Zuweisung(parallel assignment)erlaubt in einer Zuweisung mehrere Werte zuzuweisen
a, b = 7, 9 print a # => 7 print b # => 9
a, b, c = 7, 9 print a # => 7 print b # => 9 print c # => nil
a, b = 7, 9, 11 print a # => 7 print b # => 9
Die Parallele Zuweisung ohne Methodenaufruf wird von vielen Ruby-Programmierern als un- übersichtlich abgelehnt. Auch das Code-Qualitätswerkzeug Rubocop bemängelt sie als schlechten Stil.
Kann auch bei Methoden eingesetzt werden, um mehrere Werte zurückzugeben
def pair
return 'a', 42 end
x, y = pair
1 Einführung 1.14 Bedingte Zuweisung [20]
print x # => 'a' print y # => 42
Der Hauptanwendungszweck von parallelen Zuweisungen ist die Rückgabe von mehreren Werten aus einer Methode. Im Gegensatz zu Java, wo man eine eigene Klasse verwenden muss, um mehr als einen Wert zurückzugeben oder mitObject[]tricksen muss, kann eine Methode in Ruby beliebig viele Werte zurückgeben.
Das oben dargestellte Beispiel sähe in Java wie folgt aus:
public class ReturnDemo { public static class Pair {
public String x;
public int y;
public Pair(String x, int y) { this.x = x;
this.y = y;
} }
public static Pair pair() { return new Pair("a", 42);
}
public static void main(String[] args) { Pair p = pair();
System.out.println(p.x);
System.out.println(p.y);
} }
1.14 Bedingte Zuweisung [20]
■ ||=→Kombination aus||und=-Operator
■ ||=führt nur dann eine Zuweisung durch, wenn die linke Seitenilist
Mit bedingter Zuweisung
a = [ 'A' ] a[0] ||= 'X' a[1] ||= 'Y' print a # => A, Y
Ruby Grundlagen Seite 13
1 Einführung 1.15 Kommentare [21]
Ohne bedingte Zuweisung
a = [ 'A' ]
a[0] = 'X' if a[0].nil?
a[1] = 'Y' if a[1].nil?
print a # => A, Y
Ruby unterstützt mit derBedingten Zuweisungeine Abkürzung für die Zuweisung von Variablen abhängig davon, ob sienilsind oder nicht. Sie ist im Prinzip eine Kurzform des||-Operators, der in Ruby auf beliebige Objekte angewandt werden kann und dann folgende Logik besitzt:
Der Ausdrucka || bhat den Wert
■ vonawennanichtnilist
■ vonbwennanilist
Somit ist der Ausdrucka ||= bnur eine Kurzform füra = a || b. Ohne den||-Operator könnte man auch schreiben:a.nil? ? b : a.
1.15 Kommentare [21]
■ Zeilenkommentaremit#
■ Blockkommentarezwischen=beginund=end
# Kommentar
if a > 0 then a = 7 end # Zeilenkommentar
=begin
Ein mehrzeiliger Kommentar, der auch # enthalten darf
=end
Wie bei den allermeisten Skriptsprachen wird in Ruby#als Zeichen für Zeilenkommentare ver- wendet. Es entspricht damit dem//in Java und C++. Zusätzlich kann man mit=beginund=end Blockkommentare markieren (entspricht/*und*/in Java, C und C++).
1.16 Namenskonventionen [22]
■ Lokale Variablen:name,fisch_kopf,_mit_underscore
■ Methodennamen:add,enhance_flavor
■ Instanzvariablen:@name,@last_call
■ Klassenvariablen:@@address,@@class_var
■ Globale Variablen:$debug,$CUSTOMER
1 Einführung 1.17 Methodennamen [23]
■ Klassennamen:String,MyClass,DataBaseHandler
■ Konstanten:SPEED_LIMIT,DEBUG
DieNamenskonventionenfür die Benennung von Variablen unterscheiden sich von denen in Java.
Zusätzlich gibt es noch die Zeichen@und$, die zur Markierung von speziellen Variablentypen verwendet werden.
■ Lokale Variablenwerden grundsätzlich kleingeschrieben. Wenn sie aus mehreren Worten bestehen, werden diese durch einen Underscore (_) voneinander getrennt.
■ Methodennamenwerden analog zu den lokalen Variablen benannt.
■ Instanzvariablenwerden ebenfalls wie lokale Variablen benannt, allerdings muss ein@voran- gestellt werden. Details zu Instanzvariablen werden später noch erläutert.
■ Klassenvariablenwerden wie lokale Variablen benannt, allerdings muss ein@@vorangestellt werden. Auch dieses Thema wird noch vertieft.
■ Globale Variablenwerden wie lokale Variablen benannt, allerdings muss ein$vorangestellt werden. Globale Variablen sollten nur sehrbegrenztbenutzt werden.
■ Klassennamenwerden (wie in Java) in der sog. Camel-Notation angegeben. Die beginnen mit einem Großbuchstaben. Wenn sie aus mehreren Wörtern bestehen, werden diese durch einen weiteren Großbuchstaben markiert.
■ Konstantenwerden (wie in Java) nur in Großbuchstaben benannt. Wörter werden durch Underscore (_) getrennt.
1.17 Methodennamen [23]
■ Methoden können Sonderzeichen enthalten
■ Konvention für letztes Zeichen des Namens
▶ !: Methode verändert das Objekt oder ist „gefährlich“
▶ ?: Methode liefert einen booleschen Wert
▶ =: Methode steht links in einer Zuweisung
Eine weitere Besonderheit von Ruby ist, dass Sonderzeichen in Methodennamen erlaubt sind.
Hierbei gibt es einige Konventionen: Methoden, die mit!enden gelten als „gefährliche“ Methoden, d. h. es handelt sich um Methoden, die das Objekt, auf dem sie aufgerufen werden verändern. Häufig gibt es dann zwei Varianten einer Methode, eine mit!und eine ohne. Die Methode ohne!verändert das Objekt nicht und gibt ein neues Objekt zurück, die mit!verändert es.
s = "Hallo"
t = s.gsub(/l/, '') puts s #=> 'Hallo' puts t #=> 'Hao'
Ruby Grundlagen Seite 15
1 Einführung 1.18 Zeilenorientierung [24]
s.gsub!(/l/, '') puts s #=> 'Hao'
Methoden, die auf ein?enden, liefern immer einen booleschen Wert zurück und können daher direkt in einer Kontrollstruktur verwendet werden.
a = [ 'a', 'b', 'c' ] if a.include?('a')
puts "'a' gefunden"
end
Methoden mit einem= im Namen entsprechen den sog. „Settern“ in Java, d. h. es handelt sich um Methoden, die einem Attribut eines Objektes einen Wert zuweisen. Hier erlaubt es Ruby, das Gleichheitszeichen mit einem Leerzeichen abzutrennen, sodass der Methodenaufruf wie eine Zuweisung aussieht.
class A
# Setter
def name=(name)
@name = name end
# Getter def name
@name end end a = A.new
a.name = 'Thomas' puts a.name #=> Thomas
1.18 Zeilenorientierung [24]
Ruby istzeilenorientiert
■ Ausdrücke enden mit der Zeile
■ Zeilenende kann mit\escaped werden
■ Wenn dem Parser klar ist, dass das Statement nicht beendet ist, kann der\entfallen
■ Mehrere Statements in einer Zeile werden mit;getrennt
1 Einführung 1.19 BEGIN und END-Blöcke [25]
a = 1 b = 2; c = 3
d = 4 + 5 + # kein \ nötig 6 + 7
e = 8 + 9 \ + 10
Anders als in Java, muss man in Ruby ein Statement nicht explizit mit einem;beenden, sondern Ruby geht davon aus, dass jedes Statement am Ende der Zeile beendet ist.
Will man davon abweichend mehrere Statements in eine Zeile schreiben, muss man sie durch; trennen. Das ist aber in Ruby sehr unüblich, sodass die Regel gilt:ein Statement, eine Zeile. In dieser Vorlesung kommt diese Schreibweise manchmal vor aber nicht, um diesen Stil zu propagieren, sondern damit der Quelltext auf eine Folie passt.
Manchmal will man ein Statement aus optischen Gründen auf mehrere Zeilen verteilen. Generell kann man durch einen Backslash (\) am Ende der Zeile signalisieren, dass man das Statement fortsetzen will. Wenn Ruby aufgrund der Syntax erkennen kann, dass das Statement noch nicht beendet sein kann, z. B. weil noch eine Klammer zu schließen ist oder ein Operand fehlt, kann man auf den Backslash verzichten. In den meisten Fällen von mehrzeiligen Statements kann er daher entfallen.
def viele_parameter(*a) end
viele_parameter(1, 2, 3, 4, 5, 6, 7, 8)
1.19 BEGIN und END-Blöcke [25]
Quelltext kannBEGIN- undEND-Blöcke enthalten
■ BEGIN-Blöcke werden ausgeführt, wenn die Datei geladen wird
■ END-Blöcke werden nach dem Ende des Programms ausgeführt BEGIN {
puts 'Anfang' }
puts 'In der Mitte' END {
Ruby Grundlagen Seite 17
1 Einführung 1.19 BEGIN und END-Blöcke [25]
puts 'Ende' }
Kapitel 2
Kontrollstrukturen
2.1 Überblick [27]
Ruby hat alsKontrollstrukturen
■ if,elsifundelse
■ unless,else
■ case
Die Kontrollstrukturen von Ruby sind ähnlich zu anderen Programmiersprachen. Man beachte, dass es in Rubyelsifheißt und nichtelse ifwie in Java oderelifwie in Python. Ein Spezialfall ist dasunless, das weiter unten noch genauer betrachtet wird.
2.2 if [28]
■ Bedingungen mitif,elsifundelse
■ ifwird mitendbeendet
■ thenwird nur benötigt, wennifund Code in einer Zeile stehen Syntax
if condition [ then ] code
elsif condition [ then ] code
elsecode end
Dieif-Bedingungin Ruby unterscheidet sich nicht von anderen Programmiersprachen. Die einzige Besonderheit ist, dass es für denelse-if-Zweig ein eigenes Schlüsselwortelsifgibt. In Java, C
Seite 19
2 Kontrollstrukturen 2.2 if [28]
und C++ existieren gar keine speziellen else-if_-Zweige, sondern diese werden einfach durch die Kombination eineselsemit einem darauffolgendenifsimuliert.
Dasthenimifist optional. Man kann es verwenden, wenn die Bedingung in einer Zeile geschrieben werden soll und man kein;verwenden möchte. Ansonsten wird es häufig einfach weggelassen.
a = 5
# Variante mit then
if a < 4 then puts 'kleiner 4' end
# Variante ohne then
if a < 4; puts 'kleiner 4' end
# Mehrzeilig, normalerweise ohne then if a < 4
puts 'kleiner 4' end
Beispiel: if
today = Time.now if today.saturday?
puts "Gartenarbeit"
elsif today.sunday?
puts "Entspannen"
else
puts "Hochschule"
end
if als modifier
puts "Arbeiten" if !Time.now.sunday?
Ruby erlaubt es, die meisten Kontrollstrukturen als sogenannteModifierzu verwenden, d. h. die Kontrollstruktur wird hinter das Statement geschrieben und „modifiziert“ es insofern als dass es nur ausgeführt wird, wenn die Bedingung erfüllt ist. Das wirkt auf den ersten Blick ungewohnt, hat aber den großen Vorteil, dass die Kontrollstrukturen den Blick auf die eigentliche Programmlogik nicht verstellen, insbesondere, wenn das Statement im Normalfall ausgeführt wird.
Beispiel: if
today = Time.now su = today.sunday?
sa = today.saturday?
2 Kontrollstrukturen 2.3 unless [31]
if sa then puts "Gartenarbeit" elsif su then puts "Entspannen" end
2.3 unless [31]
■ Negative Bedingungen können mitunlessanstattifausgedrückt werden
■ Keinelsif unless a.nil?
puts "a ist nicht nil"
else
puts "a ist nil"
end
unless als modifier
puts "Arbeiten" unless Time.now.sunday?
Eine absolute Besonderheit von Ruby sind spezielle Kontrollstrukturen für die Negation der Be- dingung, hier dieunless-Bedingung. Diese Kontrollstrukturen führen den dazugehörigen Code aus, wenn die Bedingungnichtwahr ist. D. h. in Ruby schreibt man üblicherweise nichtif !a, sondern unless a. Es gibt allerdingskeineelse-unless-Konstruktion.
Somit entspricht der Code im Beispiel oben:
if !a.nil?
puts "a ist nicht nil"
else
puts "a ist nil"
end
2.4 Was ist true? [32]
truthiness
■ Nurnilundfalsefalse
■ Alle anderen Werte sindtrue
Ruby Grundlagen Seite 21
2 Kontrollstrukturen 2.5 Double Bang Operator [33]
a = nil
if a then puts 'Wert ist true' else puts 'Wert ist false' end b = ""
if b then puts 'Wert ist true' else puts 'Wert ist false' end
Ausgabe
Wert ist false Wert ist true
Die Frage, wann ein Ausdruck zutrueevaluiert wird, ist in einigen Skriptsprachen sehr unglücklich geregelt. So gibt es zu der Frage, was in JavaScript alles wahr ist lange Abhandlungen.
So sind inJavaScriptz. B.false
■ false
■ 0
■ =0
■ NaN
■ null
■ undefined
■ ''(der leere String)
Ruby hat sich für eine sehr einfache Lösung entscheiden: Es ist allestrueaußer genau zwei Werten, nämlichfalseselbst undnil. Man spricht hier auch von dertruthiness.
Hierbei muss man darauf achten, dass sich Ruby-Programmierer oft nicht die Mühe machen, zwischennilundfalsezu unterscheiden, da beises sowieso falsch ist. D. h. Funktionen, aus denen man vielleicht einen Wahrheitswert erwartet, kommt einnil. Dies ist so lange kein Problem, wie man den Rückgabewert nur mitifetc. verarbeitet. Braucht man auf jeden Fall einen booleschen Wert, bietet sich die Verwendung des Double Bang Operators !! an, der einen Wert in einen booleschen umwandelt.
2.5 Double Bang Operator [33]
double bang operator!!
■ wandelt jeden Wert in einen booleschen Wert um
■ technisch eine doppelte Negation (!)
a = [ !!0, !!"", !!"h", !!nil, !!true, !!false, !![], !!{}, !! lambda {} ] puts a.inspect
2 Kontrollstrukturen 2.6 Case [34]
Ausgabe
[true , true , true , false , true , false , true , true , true ]
Da man in Ruby sowieso alle Ausdrücke in Kontrollstrukturen verwenden kann, muss man viel seltener als in Java eine Konvertierung in einen booleschen Wert vornehmen. Will man dies aber trotzdem machen, verwendet man dendouble bang operator!!, der genaugenommen einfach nur eine doppelte Negation ist. Auf jeden beliebigen Ruby-Ausdruck angewandt, bekommt man dann dessen Wahrheitswert, gemäß den erläuterten Regeln zur Truthiness, zurück.
2.6 Case [34]
■ Case-Statement ähnlich zu anderen Sprachen
■ keinbreaknötig
Syntax
case VARIABLE when WERT [then ] when WERT [ then ]...
...
else...
end
Dascase-Statement von Ruby ist sehr ähnlich zu dem von Java, mit dem Unterschied, dass 1. dasswitchaus Java in Rubycaseheißt,
2. dascaseaus Java in Rubywhenheißt, 3. dasdefaultaus Java in Rubyelseheißt,
4. man in Ruby keinbreakbenötigt, weil es keine sog.fall-through logicgibt, d. h. es werde nichtdie folgendenwhenStatements ebenfalls ausgeführt und
5. man nicht nur Ganzzahlen und Strings imwhenverwenden kann, sondern jeden Datentyp, der die===-Methode überschreibt.
Die Verwendung desthenist analog zuif: Es ist nur nötig, wenn Bedingung und Aktion in einer Zeile stehen und man das Semikolon (;) sparen möchte.
Beispiel: case
age = 3 case age
Ruby Grundlagen Seite 23
2 Kontrollstrukturen 2.6 Case [34]
when 1, 2 puts 'Baby' when 3, 4
puts 'Kleinkind' elseputs 'Kind' end
Ausgabe
Kleinkind
In Java sähe dieselbe Auswahl wie folgt aus.
int age = 3;
switch (age) { case 1:
case 2:
System.out.println("Baby");
break;
case 3:
case 4:
System.out.println("Kleinkind");
break;
default:
System.out.println("Kind");
}
Statement gibt selbst wieder einen Wert zurück, d. h. es wird evaluiert
age = 3
bezeichnung = case age when 1, 2 then 'Baby' when 3, 4 then 'Kleinkind' else 'Kind'
end
puts bezeichnung
2 Kontrollstrukturen 2.7 Case und Ranges [37]
Ausgabe
Kleinkind
Eine sehr praktische Eigenschaft von allen Kontrollstrukturen in Ruby ist, dass sie selbst ein Aus- druck sind, der einen Wert liefert. Man muss also in den Ästen gar keine Zuweisung an eine Variable machen, sondern kann direkt den Wert angeben, der von der Kontrollstruktur zurückgege- ben werden soll, wenn der jeweilige Ast genommen wird. Dadurch vereinfachen sich viele typische Aufgaben erheblich.
Ohne dieses Feature müsste man das obige Programm schreiben als:
age = 3 case age
when 1,3
bezeichnung = 'Baby' when 3,4
bezeichnung = 'Kleinkind' elsebezeichnung = 'Kind' end
2.7 Case und Ranges [37]
Dascase-Statement kann gut zusammen mit Ranges verwendet werden
value = 79
as_string = case value when 1..50 then "low"
when 51..75 then "medium"
when 76..100 then "high"
else "unknown"
end
puts as_string # -> high
In Java müsste man an dieser Stelle auf einswitchverzichten und das Ganze als (fehleranfällige) if-Anweisung schreiben:
Ruby Grundlagen Seite 25
2 Kontrollstrukturen 2.7 Case und Ranges [37]
int value = 79;
String asString;
if (1 <= value && value <= 50) { asString = "low";
}else if (51 <= value && value <= 75) { asString = "medium";
}
else if (76 <= value && value <= 100) { asString = "high";
} else {
asString = "unknown";
}
System.out.println(asString); // -> high
Wehe man die Werte passen nicht aneinander oder man verwechselt ein<=mit einem<; schon gibt es schwer zu findende Fehler, die nur unter seltenen Bedingungen auftreten.
Kapitel 3
Schleifen
3.1 Kopfgesteuerten while-Schleife [39]
Syntax der kopfgesteuertenwhile-Schleife(while-loop)
while conditional [do]
endcode
doist optional, außer die Schleife wird in einer Zeile formuliert
i = 0 while i < 5
puts "i ist #{i}"
i +=1 end
Obwohl es in Ruby die in der Programmierung üblichen Schleifen gibt, wird man in echten Ruby- Programmen nur selten Schleifen antreffen. In Ruby ist es üblich, die allermeisten Probleme über Iteratoren zu lösen, d. h. über das Anwenden einer Funktion auf alle Elemente einer Collection (Array oder Hash). Dieses Vorgehen wird im Kapitel zu Containern näher erläutert.
Das obige Beispiel würde man in Ruby normalerweise wie folgt ohne Schleife über einen Iterator schreiben:
5.times { |i| puts "i ist #{i}" }
Dieser Ansatz stammt aus der Funktionalen Programmierung. Man spricht auch von einerinternen Iterationwohingegen die normalen Schleifen mitwhileetc. eineexterne Iterationdarstellen. Der große Vorteil einer internen Iteration besteht darin, dass sie
Seite 27
3 Schleifen 3.2 while als Modifier [40]
■ weniger fehleranfällig ist, weil keine Indices von Hand geführt werden müssen und
■ parallelisierbar ist, d. h. im Zusammenhang mit der parallelen Programmierung besser ver- wendet werden kann.
Die Parallelisierbarkeit ergibt sich daraus, dass ohne eine äußere Iteration die übergebene Funktion keine Änderungen machen kann, die mehr als ein Element betreffen. Außerdem kann – wenn die Funktion zusätzlich noch seiteneffektfrei ist – sicher davon ausgehen, dass eine Aufspaltung der Operation auf mehrere parallele Threads dasselbe Ergebnis bietet, wie eine lineare Ausführung. Das Beispiel oben ist übrigens nicht frei von Seiteneffekten, weil die Ausgabe perputsein Seiteneffekt ist. Bei einer Parallelisierung würden die Werte nicht mehr in der korrekten Reihenfolge 1, 2, 3 . . . ausgegeben.
Frei von Seiteneffekten wäre aber z. B. folgende interne Iteration, die ausschließlich die Elemente des Arraysaverändert:
a = [1, 2, 3]
b = a.map { |e| e**2 }
3.2 while als Modifier [40]
whilekann wieforoderuntilauch als Modifier benutzt werden
while in normalen Form
while i < 5 do i += 1 end
while als modifier
i += 1 while i < 5
Wie auch die Kontrollstrukturen kann man die Schleifen hinter das Statement schreiben, auf das sie sich beziehen sollen. Auch diese Konstruktion ist aber wegen der Dominanz der internen Iteration in Ruby eher selten.
3.3 Fußgesteuerte while-Schleife [41]
Syntax der fußgesteuertenwhile-Schleife(while-loop)
begin
endcodewhile conditional
3 Schleifen 3.4 until-Schleife [42]
i = 9 begin
puts "i ist #{i}"
i += 1 end while i < 5
Ausgabe
i ist 9
3.4 until-Schleife [42]
until-Schleife
■ läuft, solange die Bedingung falsch ist
■ until Aentsprichtwhile !A
i = 0 until i == 5
puts "i ist #{i}"
i +=1 end
i = 0 begin
puts "i ist #{i}"
i += 1
end until i == 5
Dieuntil-Schleife entspricht derwhile-Schleife mit dem Unterschied, dass die Bedingung negiert wird, d. h. die Schleife läuft so lange die Bedingungnichtzutrifft. Damit istuntil Aäquivalent mit while !A, wobeiAein Ausdruck ist, der sich als Wahrheitswert interpretieren lässt.
Ruby Grundlagen Seite 29
3 Schleifen 3.5 for-Schleife [43]
3.5 for-Schleife [43]
Syntax derfor-Schleife
for variable [, variable ...] in expression [do]
endcode
Beispiel
for i in 0..5 puts "i ist #{i}"
end
Ausgabe
i ist 0 i ist 1 i ist 2 ...
words = [ 'Hallo', 'World', '!' ] for w in words
puts "w: #{w}"
end
Ausgabe
w: Hallo w: World w: !
Diefor-Schleife in Ruby ist deutlich weniger leistungsfähig als das entsprechende Konstrukt in Java oder C. In Ruby kann sie ausschließlich über die Elemente einer Collection oder einen Range laufen. Die Angabe einer Laufvariable oder das eigenständige Modifizieren dieser Variable ist nicht möglich. Sie entspricht damit eher der sog. for-each-Schleife in Java als der for-Schleife.
Das obige Beispiel würde in Java wie folgt aussehen:
String[] words = { "Hallo", "World", "!" };
for (String word : words) {
System.out.println("w: " + word);
}
3 Schleifen 3.6 Kontrolle der Schleife [45]
3.6 Kontrolle der Schleife [45]
Schlüsselworte für dieKontrolle der Schleife
■ break: verlässt die innerste Schleife
■ next: startet die nächste Iteration der innersten Schleife
■ redostartet die nächste Iteration der innersten Schleifeohne die Bedingung zu überprüfen oder den Zähler zu erhöhen
Dasbreak-Statement in Ruby entspricht dem von Java und hat auch dasselbe Verhalten. Allerdings besitzt Ruby keinelabeled break, d. h. man kann immer nur die aktuelle Schleife verlassen, nicht aber eine weiter außen liegende. Will man aus mehreren Schleifen heraus-„breaken“, muss man eine zusätzliche Variable verwenden und diese in jeder umgebenden Schleife abprüfen.
Beispiel: break
for i in 0..5 if i > 2
break end
puts "Wert von i ist #{i}"
end
Ausgabe
Wert von i ist 0 Wert von i ist 1 Wert von i ist 2
Dasnext-Statement in Ruby entspricht demcontinue-Statement in Java.
continue in Java
for (int i = 0; i <= 5; i++) { if (i < 2) {
continue;
}System.out.println("Wert von i ist " + i);
}
Beispiel: next
for i in 0..5 if i < 2 endnext
puts "Wert von i ist #{i}"
end
Ruby Grundlagen Seite 31
3 Schleifen 3.7 Scope der Variablen [49]
Ausgabe
Wert von i ist 2 Wert von i ist 3 Wert von i ist 4 Wert von i ist 5
Dasredo-Statement in Ruby springt nicht zum Schleifenkopf, sondern zum ersten Statementdanach.
Insofern wird weder die Schleifenbedingung überprüft, noch bei derfor-Schleife die Laufvariable weiterbewegt. Die Anwendungsbereiche fürredo sind beschränkt und bei der Nutzung lauert immer die Gefahr einer Endlosschleife.
Beispiel: redo
for i in 0..5 if i < 2
puts "Wert von i ist #{i}"
redo endend
Ausgabe
Wert von i ist 0 Wert von i ist 0 Wert von i ist 0 ...
3.7 Scope der Variablen [49]
■ Schleifen schaffen keinen neuen Scope, d. h. Variablen sind auch nach der Schleife noch sichtbar
■ Variablen in Blöcken sind lokal zum Block (es sei denn, sie waren schon vorher definiert)
i = 0 while i < 5
my_i = i i += 1 end
puts my_i # 4
3 Schleifen 3.7 Scope der Variablen [49]
5.downto(1) { |v| my_v = v } puts my_v
# => NameError: undefined local variable or method `my_v' for main:Object
DerScope von Variablenin Ruby unterscheidet sich von Java. In Java sind Variablen immer lokal zu dem Java-Block, in dem sie definiert wurden. In Ruby hingegen schaffen Schleifen und Kon- trollstrukturen keinen neuen Scope (Sichtbarkeitsbereich). Damit sind die Variablen auch nach der Schleife bzw. Kontrollstruktur noch sichtbar.
Scope in Java
for (int i = 0; i < 10; i++) { // tu was
int k = i * 2;
}// weder k noch i sind hier noch sichtbar
Ein Ruby-Block ist ein eigener Sichtbarkeitsbereich. Allerdings hat er technisch nichts mit dem Java-Block zu tun:
■ EinJava-Blockgruppiert eine beliebige Anzahl von Statements so, dass sie wie ein einzelnes Statement verwendet werden können.
■ EinRuby-Blockdefiniert ein Stück ausführbaren Code, der an eine Methode übergeben werden kann. Er entspricht daher viel eher demJava-Lambdaals einem Block in Java.
Ruby-Blöcke werden noch ausführlich im Kapitel zu Containern behandelt.
Ruby Grundlagen Seite 33
Kapitel 4
Ausdrücke
4.1 Alles hat einen Rückgabewert [51]
■ Wo immer möglich geben Ruby-Statements einen Wert zurück
a = b = c = 0 # => 0
[ 3, 1, 7, 0 ].sort.reverse # => [7, 3, 1, 0]
x = if a == 0 'Null' else
'Nicht Null' end
puts x # => "Null"
Wie bereits erläutert, sind in Ruby alle Daten ein Objekt. Genauso konsequent hat alles in Ruby einen Rückgabewert, z. B. Kontrollstrukturen, Klassendefinitionen, Methodendefinition, Zuweisungen etc.
■ Klassendefinition→nil
■ Methodendefinition→Name der Methode als Symbol
■ Zuweisung→Wert rechts vom Gleichheitszeichen
Durch diese Eingenschaft kann man an vielen Stellen die Programmierung vereinfachen.
4.2 Operatoren als Methoden [52]
■ Die meisten Operatoren (z. B.+,*,=) in Ruby sind als Methoden realisiert
■ Operatoren der Standardklassen können überschrieben werden (Vorsicht!)
■ Klassen können Operatoren nach den eigenen Bedürfnissen implementieren
4 Ausdrücke 4.3 Beispiel: Operatoren implementieren [53]
a, b, c = 1, 2, 3 a * b + c # => 5 (a.*(b)).+(c) # => 5
Ruby implementiertOperatorenselbst wieder als Methoden. Durch die syntaktische Besonderheit, dass man zusätzlich den Punkt bei der Benutzung einer solchen Methode weglassen kann, sehen diese Methoden aus, wie die Operatoren anderer Programmiersprachen. Anstatta + bkann man daher auch schreibena.+(b), was zeigt, dass+eine ganz normale Methode, mit einem sehr kurzem und speziellem Namen, ist.
Da die Operatoren in den Standardklassen (z. B. Integer oder Float) als Methoden implementiert sind, kann man diese überschreiben. Allerdings sollte man dies nur mit größter Vorsicht tun und vor allem darauf achten, dass man das normale Verhalten nicht verändert.
# Schlechtes Beispiel class Integer
# Plus Operator ersetzen def +(other)
self * other endend
puts 5 + 5 #=> 25
4.3 Beispiel: Operatoren implementieren [53]
class Cout def <<(str)
print str self endend
cout = Cout.new
cout << "Hallo" << " Welt" << "!" << "\n"
Ausgabe
Hallo Welt!
Ruby Grundlagen Seite 35
4 Ausdrücke 4.4 Betriebssystem-Kommandos [55]
Dieses Beispiel zeigt, wie man in einer eigenen Klasse einen Operator implementiert. Es simuliert das Verhalten vonstd::coutaus<iostream>in C++. Hier können beliebige Strings über den
<<-Operator auf der Konsole ausgegeben werden.
Der Operator wird einfach durch die Methode mit dem entsprechenden Namen<<implementiert.
Damit die Aufrufe aneinander gereiht werden können, gibt die Methode als letztes eine Referenz auf das eigene Objekt zurück. Hierdurch kann man beliebig viele Aufrufe von<<hintereinander- schreiben – eine Technik, die manmethod chainingnennt.
Das nächste Beispiel zeigt, wie man den[]-Operator implementieren kann.
class Sum def [](index)
sum = 0
1.upto(index).each { |e| sum += e } endsum
end
sum = Sum.new
puts sum[100] # => 5050
Hier ist zu beachten, dass die Methode[]heißt, obwohl der Parameter dann später zwischen den eckigen Klammern steht. Dieses Beispiel gibt die Summe der Zahlen bis zum angegebenen Wert zurück. Man würde im Beispiel eigentlich diereduce-Methode benutzen, die aber erst im Kapitel zu Containern eingeführt wird. In diesem Fall sähe das Beispiel wie folgt aus, greift aber sehr weit vor:
class Sum def [](index)
(1..index).reduce(:+) endend
4.4 Betriebssystem-Kommandos [55]
■ Mit dem Backtick “‘ oder%x{ }eingeschlossene Strings werden vom Betriebssystem ausge- führt
■ Die Ausgabe des Kommandos ist der Rückgabewert
■ Der Exit-Code ist in der Variable$?(TypProcess::Status) zu finden d = `date`
puts d # => "Do 15 Jun 2017 23:11:53 CEST\n"