© Liashko/Shutterstock.com
Metaprogrammierung mit Ruby

Objektorientierter Rubin


Kodierer und Compiler leben seit Anbeginn der Zeit nach einem strengen Konzept der Arbeitsteilung: Der Programmierer schreibt Code, der von seiner Entwicklungsumgebung ausgeführt wird. Wie dieses Konzept mit der spannenden Historie von Ruby zusammenhängt, erläutert dieser Artikel.

Das Konzept der Metaprogrammierung versucht, diese althergebrachte Harmonie zu stören. Die Idee dahinter ist, dass Code – zumindest theoretisch – Code generieren könnte. An sich ist diese Idee nichts Neues; Windows 1.0 nutzte zwecks Performancesteigerung eine Blitter-Routine, die sich vor der Anwendung „selbst kompilierte“ und so unnötige Sprünge und Selektionen beseitigte.

Was ist Ruby?

Ein japanischer Student namens Yukihiro Matsumoto – Ruby-Nutzer bezeichnen ihn normalerweise als „Matz“ – war mit den im Jahre 1993 existierenden Skriptsprachen unzufrieden. Er empfand Perl als nicht ausreichend mächtig, während Python seiner Ansicht nach nicht ausreichend objektorientiert war. Als oberste Maxime avisierte er, dass die Nutzung der Programmiersprache dem Entwickler Freude bereiten müsse. Dies wirkt sich auch auf die Community aus: Dem Autor sind nur wenige Gemeinschaften von Entwicklern bekannt, die einen ähnlich angenehmen Umgangston pflegen.

Das Resultat seiner Bemühungen war eine streng objektorientierte Sprache, die nicht statisch typisiert ist. Stattdessen kommt das aus JavaScript bekannte Konzept des Duck Typings zum Einsatz: Wenn ein Objekt die notwendigen Methoden implementiert, so gilt es als zur Klasse zugehörig. Dies ist für die Metaprogrammierung wichtig. Ruby erlaubt Entwicklern das Einpflegen von neuen Funktionen und Variablen zur Laufzeit: Es ist ohne Probleme möglich, eine bestehende Klasse als Reaktion auf ein Ereignis um eine neue Funktion zu erweitern. Der Aufbau dieser Methode darf von den vorliegenden Parametern beeinflusst werden, wodurch die Sprache eine immense Flexibilität erreicht.

Von C++ oder Java umsteigende Entwickler stolpern zudem über zwei kleine Besonderheiten des Sprachstandards: Erstens sind die Regeln für boolesche Typen wesentlich strikter, zweitens liefert alles – also auch eine Selektion – einen Wert zurück.

Mine auf!

Nach diesen einführenden Überlegungen ist es an der Zeit, erste Schritte in die Welt von Ruby zu setzen. Wer, wie der Autor dieser Zeilen, unter Unix arbeitet, muss normalerweise nur den Befehl irb eingeben, um einen Ruby-Interpreter zu aktivieren. Unter Windows lebende Entwickler finden unter https://www.ruby-lang.org/de/downloads/ ein fertiges Installationspaket. Unser erstes Programm mag auf den ersten Blick banal erscheinen, illustriert aber eine für spätere Versuche immens wichtige Vorgehensweise. Den Quellcode zeigt Listing 1.

Listing 1

tamhan@ubuntu:~$ irb irb(main):001:0> def sagWas(iwas) irb(main):002:1> puts "Ich sage #{iwas}" irb(main):003:1> end => nil irb(main):004:0> sagWas("oioioi") Ich sage oioioi => nil

irb führt die eingegebenen Befehle „interaktiv“ aus. Das bedeutet, dass jedes eingegebene Kommando sofort in die Ruby Engine wandert und dort ausgewertet wird. Wir beginnen mit der Definition einer sagWas genannten Funktion, die einen Parameter namens iwas entgegennimmt. Kenner von JavaScript und anderen dynamisch typisierten Sprachen wundern sich nicht darüber, wo die Deklaration des Datentyps bleibt – er wird dynamisch festgelegt.

Exotischer ist in dieser Hinsicht der Aufbau der Funktion. Er beginnt mit def und endet mit end – geschwungene Klammern finden Sie in Ruby nur höchst selten. Das gilt auch für das in C und Java beliebte Semikolon: Ruby-Kommandos enden normalerweise mit dem Zeilentrennzeichen. Der Aufruf von sagWas mit dem Parameter oioioi ist dann wieder komplett konventionell.

Befehle und Fälschungen

Wer eine Funktion aufruft, erwartet normalerweise eine Antwort bzw. einen Rückgabewert. irb zeigt „unbehandelte“ Rückgabewerte in Form einer mit einem ASCII-Pfeil beginnenden Zeile an. In unserem Fall führen sowohl die Definition der Funktion als auch ihr Aufruf zu einem Rückgabewert vom Typ nil. Ruby-erfahrene Entwickler nutzen diese Besonderheit der Programmiersprache gerne zum Abkürzen von Quellcode. Ein klassisches Beispiel dafür ist eine Selektion, die einen Wert wie in Listing 2 zurückgibt.

Listing 2

irb(main):023:0> x=0 => 0 irb(main):024:0> y=0 => 0 irb(main):025:0> if x==0 irb(main):026:1> true irb(main):027:1> end => true 

Damit ist nur mehr die Frage nach der Bedeutung von nil offen. C, Java und Python sind bei der Auslegung von Boolean-Werten vergleichsweise gnädig. Eine mit dem Wert 0 befüllte Integer-Variable geht in den meisten Programmiersprachen problemlos als false durch. Ruby tickt an dieser Stelle etwas anders, was wir anhand des kleinen Beispiels in Listing 3 demonstrieren wollen. Es findet sich in leicht abgeänderter Form in der offiziellen FAQ für Umsteiger – anscheinend haben viele Entwickler mit dieser Besonderheit ihre liebe Not.

Listing 3

irb(main):014:0> if 0 irb(main):015:1> puts "0 ist wahr" irb(main):016:1> else irb(main):017:1* puts "0 ist falsch" irb(main):018:1> end 0 ist wahr => nil irb(main):019:0> if false irb(main):020:1> else irb(main):021:1* puts "false ist falsch" irb(main):022:1> end false ist falsch => nil 

Die Erklärung dieses Zusammenhangs ist ähnlich philosophisch wie die alte Frage nach der Einheit eines einheitenlosen Werts: Elektroniker können sich stundenlang darüber streiten, ob das korrekte Einheitensymbol [1] oder [-] lautet. Erfreulicherweise gibt es im Fall von Ruby eine „einfachere“ Erklärung. Betrachten wir – wie so oft – die Nachbarin des Autors. Sie liebt slowakische Kekse abgöttisch und bewahrt ihren Vorrat in einer seltsam anmutenden Holzkiste auf. In Ruby ließe sich dieses Element nach folgendem Schema modellieren:

irb(main):006:0> keksBoxInhalt=1 => 1 

Wenn der Lieferdienst der Supermarktkette Tesco eine Packung weiterer Kekse liefert, so erhöht sich der Vorrat – zumindest dann, wenn man den in Ruby nicht implementierten ++-Operator nicht versehentlich einsetzt:

irb(main):007:0> keksBoxInhalt=keksBoxInhalt+1 => 2 

Nun steht am Abend die Party einer Bekannten an. Die Nachbarin weiß, dass es auf dieser Party keine Kekse geben wird. Weil sie aber nicht auf ihre geliebten Naschereien verzichten möchte, nimmt sie den Inhalt der Holzkiste mit zu ihrer Bekannten. In Ruby sieht dies so aus:

irb(main):009:0> keksBoxInhalt=keksBoxInhalt-keksBoxInhalt => 0 

Rein zufällig findet sich bei besagter Bekannten ein baugleiches Exemplar der Holzkiste. Die beiden Kisten – die zu Hause und die bei der Bekannten – sind nun insofern „identisch“, als sie beide im Moment keine Kekse beheimaten. Leider...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang