Corinna: Ein modernes und reifes Objektsystem für Perl 5

Die Programmiersprache Perl hat mit Corinna seit Version 5.38 ein ausgereifteres Objektsystem ohne die bisherige Schwachstelle der fehlenden Schlüsselworte.

In Pocket speichern vorlesen Druckansicht 18 Kommentare lesen

(Bild: Miriam Doerr, Martin Frommherz/Shutterstock.com)

Lesezeit: 14 Min.
Von
  • Herbert Breunung
Inhaltsverzeichnis

Mit der Version 5.38 stehen neue Schlüsselworte für die Objektorientierung (kurz OO) im Sprachkern von Perl zur Verfügung. Dies ist eine der tiefgreifendsten Erweiterungen seit Perl 5.0 (1994) und verdient daher eine genaue Analyse, die nebenbei etwas über die langfristige Entwicklung der Sprache aussagt. Aber auch für Nicht-Perl-Programmierer ist die Umsetzung der Neuerung durchaus erkenntnisreich, da das Perl-Entwicklungsteam hier ein grundlegendes Problem umsichtig und nachhaltig gelöst hat.

Definitionen der OO sind selten präzise und oft widerspricht eine Definition der anderen, wie auch Damian Conway – der maßgeblich zur objektorientierten Programmierung in Perl beigetragen hat – immer wieder betont. Der Ansatz von Alan Kay ist hilfreich, aber die zentrale Herausforderung der meisten Softwareprojekte bleibt: eine fast alles erstickende Komplexität. Objekte verstecken etwas davon hinter der Fassade ihrer öffentlichen Schnittstelle und begrenzen so die für den Programmierer relevante Informationsmenge. Nur zu diesem Zweck besitzen Objekte auch einen Zustand (Attribute).

In Perl 5.0, das die Objektorientierung in die Sprache einführte, gelang es nur halbherzig, dieses Verstecken umzusetzen. Denn Perl-5-Objekte sind, abgesehen von ihrem Referenz-Typ, normale Datenstrukturen. Nur die Selbstbeschränkung der Nutzer verhindert hier den Zugriff auf private Attribute.

Eine zweite Schwäche waren fehlende Schlüsselworte. Erfahrene Programmierer sehen sofort an einem bless oder use base, welche Pakete (Namensräume) Klassen sind und am my $self = shift;, welche sub als Methode fungiert. Das Perl-Entwicklungsteam hätte es aber auch Neulingen einfacher machen können, anstatt deren Einwand, Perl hätte gar keine richtige OO, nur immer wieder mit dem Hinweis auf die überlegene Mächtigkeit der Perl-OO abzubügeln.

Sachlich ist es zwar richtig, dass sich mit dem nachträglich in die Sprache eingefügten (Meta-)System jede Art von OO bauen lässt. Es verlangt jedoch einen deutlich erhöhten Schreibaufwand – auch für alltägliche Arbeiten, die in anderen, sonst wesentlich geschwätzigeren Sprachen, direkter gelöst sind. Zusammen mit dem damaligen Fehlen von Signaturen hinterließ dieser Umstand bei nicht Wenigen einen archaischen, ungelenken Eindruck.

Perl 6 sollte all das beheben, denn bereits 35 der 361 initialen Requests for Comments (RFC) adressierten Teilprobleme. Während eines überlangen Entscheidungsprozesses erwuchs aus den Antworten der RFC ein mächtiges, sehr ausgereiftes OO-System. Doch Perl 6 entwickelte sich zu einer eigenen Sprache und wurde später in Raku umbenannt. Als feststand, dass Perl 5 nicht abgelöst wird, erschuf Stevan Little mit Moose ein Objektsystem, das die Raku-OO so gut es ging nach Perl 5 portierte und durch Fähigkeiten des Common Lisp Object System (CLOS) und anderer Sprachen erweiterte. Es folgte eine dem Goldrausch vergleichbare Phase, in der unzählige Moose-Plug-ins und etliche Moose-Alternativen entstanden. Während dieser Zeit kristallisierte sich heraus, welche Funktionalitäten wichtig sind und welche Schreibweisen sich eignen.

Als Curtis "Ovid" Poe antrat, um die neue OO unter dem Projektnamen Cor (später Corinna genannt) zu entwerfen, konnte er auf die gewonnenen Erfahrungen zurückgreifen und sich auch mit entscheidenden Köpfen hinter dieser Entwicklung beraten – darunter etwa Damian Conway und Stevan Little. Letztlich wählte er einen vorsichtigen und minimalistischen Weg, mit einer Syntax, die bewusst Verwechslungen mit Raku und Moose meidet, und mit einer Semantik, die sich nahtlos in Perl 5 einfügt.

Corinna führt auch keine neuen syntaktischen Wendungen ein und verzichtet konsequent auf Ausnahmeregelungen, wie etwa bestimmten Symbolen in Bezeichnern (große Anfangsbuchstaben oder '__') besondere Bedeutung zuzuschreiben. Die Semantik ist rein deklarativ und die Syntax hält sich an einfache, einheitliche Regeln – beides eher von Raku als von Perl 5 beeinflusst.

Die schwerwiegendste Kritik an Corinna lässt sich als Frage an die Perl-Porter (p5p) formulieren: Warum war diese Funktionalität nicht bereits zehn Jahre früher möglich, als es absehbar war, dass Perl 6 Perl 5 nicht ersetzen wird? Die größte technische Schwäche von Corinna hingegen ist der nicht vermeidbare Umstand, dass per bless entstandene Klassen und Corinna-Klassen nicht voneinander erben können und auch getrennte Basisklassen besitzen. Für eine Zusammenarbeit bleibt Entwicklerinnen und Entwicklern nur die Möglichkeit, OO per Komposition zu verwenden und Objekte der anderen Art als Attribute zu benutzen.

Corinna umfasst vier neue Schlüsselworte: class, role, method und field, wobei role wie auch etwa 70 Prozent der spezifizierten Funktionalität noch nicht mit Version 5.38 ausgeliefert werden. Die folgende Darstellung behandelt trotzdem alle wesentlichen Features und benennt, was bereits verwendbar ist. Nutzer älterer Perlversionen können Corinna (auf dem Stand von 5.38) als Modul Compat::Class:Feature einbinden.

Attribute sind Variablen, deren Geltungsbereich mit field, anstatt my, our oder state deklariert wird, was ihre Nutzung auf alle Methoden der aktuellen Klasse oder Rolle beschränkt. Logischerweise ist das nur innerhalb einer Klasse (class) oder Rolle (role) möglich. Und wie zu erwarten ist ihr Inhalt bei jedem Objekt ein anderer. Während ihrer Initialisierung lässt sich ihnen ein Wert mithilfe eines beliebigen Ausdrucks oder Anweisung zuweisen. Diese wird ausgeführt, wenn das Objekt erzeugt wird (wenn der Nutzer Klassen::Name->new(...) aufruft).

Wenn eine Anweisung nicht reicht, lässt sich auch ein ganzer Block einschieben. Dieser wird so ausgewertet, als ob ein do vor dem Block stünde.

Zwischen Variablenname und Zuweisung können beliebig viele Attribute stehen. Sie sind syntaktisch von Raku abgeschaut, aber bereits mit Perl 5.10 (2008) eingeführt. Bisher fanden sie jedoch kaum Beachtung, da sie lediglich Subroutinen zu selten verwendetem Verhalten verhelfen. In Corinna gilt für alle Schlüsselworte die einheitliche Reihenfolge: Schlüsselwort, Name, Attribute, Code. Die letzten beiden Elemente sind optional.

Das einzige bisher implementierte Feld-Attribut ist :param. Es gibt an, dass der Konstruktor ein gleichnamiges Argument akzeptiert, dessen Inhalt automatisch der Feldvariable zugewiesen wird. Weichen die Namen von Konstruktorargument und Feldvariable voneinander ab, bekommt das Attribut :param den anderen Namen als Argument.

field $timestamp :param(created) ||= time;

Class::Name->new( created => 0 );

Wäre der Feldvariable $timestamp bei Initialisierung kein Wert zugewiesen, so würde der Konstruktor mit Fehler abbrechen, wenn er keinen Wert unter dem Namen created erhält (und auch kein <code> ||= time</code> im Beispiel stünde). Im Beispiel bekam er eine Null, die allerdings nicht nach $timestamp übertragen wird, da der selbstzuweisende Operator ||= greift und dem logisch wahren Ergebnis von time den Vorrang gibt.

Geplant sind die Attribute :reader und :writer, die das Erzeugen von Methoden für lesenden und schreibenden Zugriff (Accessors) veranlassen. Auch hier sind wieder abweichende Namen als Attribut-Argument zulässig und sei es nur, um eine kombinierte Lese- und Schreibmethode zu erhalten. Denn :writer ohne Argument hängt ein set_ vor den Variablennamen. Zu beachten ist auch, dass Feldvariablen in abgeleiteten Klassen nicht sichtbar sind. Bei exotischeren Namenskonflikten kann eine Feldvariable mit :name(...) bei Initialisierung einen anderen Namen tragen als bei der späteren Benutzung in den Methoden. Auch :reader, :writer und :param beziehen sich dann auf den als ... angedeuteten Namen.

:common markiert Klassenvariablen, die einen Datenaustausch zwischen Instanzen einer Klasse erlauben und :handles(...) ermöglicht Delegation.

field $datetime :handles(time:hms, now) = 'DateTime';

In diesem Beispiel speichert das Feld $datetime ein DateTime-Objekt.

Aufrufe wie $objekt->time werden dann automatisch an das Attribut von $objekt als $datetime->hms weitergeleitet und $objekt->now wird zu $datetime->now.