Das Open-Source-Projekt Pharo modernisiert den Programmierklassiker Smalltalk. Die aktuelle Version 4.0 der Programmierumgebung, die in einer Smalltalk-VM haust, überzeugt durch eine minimalistische Syntax und die ausgereifte IDE mit sinnvoller Benutzerführung.
Wie Entwickler mit Pharo 4.0 [1] im Handumdrehen komplexe und wartbare Anwendungen programmieren, zeigt gleich das folgende Beispiel. Der Shape-Akkumulator addiert die über ein Webformular spezifizierten Eigenschaften dreidimensionaler Körper (Abbildung 1) Server-seitig auf. Er liefert das Ergebnis an den Browser des Clients zurück.
Die aktuelle Version 4.0 steht unter der MIT-Lizenz, Pharo läuft in einer in Smalltalk [2] geschriebenen virtuellen Maschine. Unter Debian 7.8 startet der Benutzer das Derivat für ältere Versionen der Glibc mit dem Befehl:
wget http://files.pharo.org/platform/ Pharo4.0-linux-oldLibC.zip && unzip Pharo4.0-linux-oldLibC.zip
Der holt das Zip-Archiv und entpackt es auf der Kommandozeile.
Pharo läuft nur auf 32-Bit-Systemen, weshalb Anwender von 64-Bit-Distributionen Kompatibilitätspakete brauchen. Weitere Hinweise zur Installation gibt die Website [3]. Abbildung 2 zeigt die IDE nach dem Aufruf von »./pharo4.0/pharo« .
Die Abbildung präsentiert die wichtigsten Werkzeuge der IDE. Links zeigt der Systembrowser Klassen und Methoden nicht nur an, sie lassen sich hier auch bearbeiten. In der Mitte sitzt das Kontext- oder auch World-Menü. Rechts oben stößt der Entwickler auf den Finder, mit dem er die Pharo-Umgebung durchsucht. Unten rechts wartet noch eine Smalltalk-Shell, Playground genannt, um Code einzugeben und auszuführen.
Obsessiv
Smalltalk kennt nur Objekte. Zahlen, Zeichenketten und Klassen sind genauso Objekte wie die Fenster unter der IDE in Abbildung 2. Neue Objekte entstehen wie unter C++ oder Java, indem der Entwickler sie instanziert. Da die Basisklasse der Beispielanwendung (Listing 1) in der gestarteten Pharo-Umgebung noch nicht existiert, erzeugt er sie mit Hilfe des Systembrowsers (Abbildung 3). Den startet er, indem er im World-Menü auf die IDE klickt.
Listing 1
Definition der abstrakten Basisklasse Shapes
01 Object subclass: #Shapes 02 instanceVariableNames: '' 03 classVariableNames: '' 04 category: 'Shapes'
Da Pharo Klassen intern thematisch sortiert nach Packages speichert, legt der Entwickler für die zu programmierenden Klassen erst das Package »Shapes« an. Dazu wählt er ein bestehendes Package aus der linken Spalte von Abbildung 3 und öffnet einen Dialog, indem er aus dem Kontextmenü den Punkt »Add package« auswählt. Das Textfeld nimmt den Namen des neuen Package entgegen, ein Klick auf »OK« erzeugt es.
Im nächsten Schritt kopiert er den Code aus Listing 1 in den Editor (Abbildung 3, unten) und übernimmt ihn über das Kontextmenü. Die Klassendefinition beginnt mit der Klasse »Object« , die als Vorlage nahezu aller Basisklassen unter Pharo dient. Der eigentliche Klassenname steht hinter dem Schlüsselwort »subclass« und dem Hashtag. Zeile 4 ordnet »Shapes« dem gleichnamigen Package zu.
Empfangsbereit
Listing 2 zeigt die Methoden der Klasse »Shapes« . Der Pharo-Anwender kopiert sie ebenfalls in den Editor und übernimmt sie übers Kontextmenü. Ihre Namen erscheinen dann in der Spalte ganz rechts (Abbildung 3). Wer eine Methode im Editor bearbeiten möchte, wählt einfach ihren Namen aus. Außerdem kann er sie über die dritte Spalte von rechts funktional einordnen, indem er sie einem Protokoll zuweist.
Listing 2
Methoden der Basisklasse Shapes
01 set: dim
02 ^ self subclassResponsibility.
03
04 surface
05 ^ self subclassResponsibility.
06
07 volume
08 ^ self subclassResponsibility.
09
10 printAsText
11 ^ '{1} has the volume: {2} and a surface of {3}' format: {(self name) asString. self volume asString . self surface asString}.
Ihre Definition beginnt mit dem Methodennamen, der in Zeile 1 »set:« lautet. Übernimmt die Methode »set« einen Parameter, schließt ihr Name stets mit einem Doppelpunkt. Ihm folgt der Name des Aufrufparameters, hier »dim« . Smalltalker nennen Methoden Nachrichten, da sie Objekte als Empfänger von Nachrichten verstehen.
Besonderes Augenmerk verlangt dabei die Präzedenz [4] von Nachrichtentypen. Dank des »^« -Operators gibt Zeile 2 den Wert des Ausdrucks »self subclassResponsibility« zurück. Der Ausdruck sendet die Nachricht »subclassResponsibility« über die Sondervariable »self« quasi an sich selbst. Die Nachricht erzwingt einen Fehler, der die Methode sowie ihre Artgenossen »surface« und »volume« als abstrakt ausweist. Der Punkt am Ende von Zeile 2 trennt dabei Ausdrücke – wie das Semikolon in C-artigen Sprachen.
Anders als »set:« bringen »surface« , »volume« und »printAsText« keinen Aufrufparameter mit. In Zeile 11 gibt »printAsText« eine formatierte Zeichenkette zurück. Die Methode »format:« ersetzt dabei die Platzhalter »{1}« , »{2}« und »{3}« in der Zeichenkette. Der Methodenaufruf übernimmt ein Feld mit den drei Ausdrücken »(self name) asString« , »self volume asString« und »self surface asString« als Aufrufparameter. Letztere ersetzen die Platzhalter positionsgetreu.
Der erste Ausdruck ermittelt den Namen der Klasse, der zweite ruft die Methode »volume« , der dritte »surface« auf. Um die drei Ausdrücke in der Zeichenkette zu verwenden, verwandelt die Methode »asString« ihre Werte jeweils in eine Zeichenkette. Dank »self« startet Smalltalk die Suche nach einer Methode stets in der abgeleiteten Klasse.
Privat
Listing 3 demonstriert die Klassendefinition der Klasse »Sphere« . Sie verwendet in Zeile 1 die Klasse »Shapes« als Vorlage. Zeile 2 definiert die Instanzvariable »radius« . In Zeile 3 ließe sich analog eine Klassenvariable definieren. Die Klassenmethoden erreicht der Entwickler übrigens auch über die Schaltfläche »class side« (Abbildung 3).
Listing 3
Definition der abgeleiteten Klasse Shpere
01 Shapes subclass: #Sphere 02 instanceVariableNames: 'radius' 03 classVariableNames: '' 04 category: 'Shapes'
Listing 4 zeigt die Methoden der abgeleiteten Klasse aus Listing 3. Bei der Methode »initialize« (Zeile 1) handelt es sich um eine Konstruktorfunktion, das heißt, Smalltalk ruft sie beim Erzeugen eines Objekts auf. Will der Entwickler sie wie im vorliegenden Fall überschreiben, darf er den Ausdruck »super initialize.« nicht vergessen. Anderenfalls ginge der Aufruf der gleichnamigen Methode der Elternklasse schief.
Listing 4
Methoden der Klasse Sphere
01 initialize 02 super initialize. 03 radius := 0. 04 05 set: dim 06 radius := dim. 07 08 surface 09 ^ 12.56 * (radius * radius). 10 11 volume 12 ^ 12.56 / 3 * radius * radius * radius.
Zeile 3 belegt die Instanzvariable »radius« mit dem Wert »0« . Die Zeichenfolge »:=« agiert als Zuweisungsoperator, das Gleichheitszeichen als Vergleichsoperator. Da Instanzvariablen wie auch Klassenvariablen unter Smalltalk immer privat sind, bedarf es entsprechender Getter- und Setter-Methoden. Eine davon ist die Methode »set:« ab Zeile 5 für die Instanzvariable »radius« . Zudem überschreibt sie die gleichnamige abstrakte Methode aus Listing 2 und vermeidet so die unerwünschte Fehlermeldung.
Die Methode »surface« (Zeile 8) liest den Wert der Instanzvariablen in Zeile 9 aus und liefert als Ergebnis die Ausdehnung der Kugeloberfläche zurück. Die Methode »volume« (Zeile 10) geht bei der Volumenberechnung ähnlich vor. Nach dem Vorbild der Klasse »Sphere« lassen sich für andere dreidimensionale Körper weitere Klassen ableiten, beispielsweise die Klasse »Cubic« für einen Würfel mit dem Volumen von a3 und einer Oberfläche von 6a2.
Anonym
Auch die Definition der abgeleiteten Klasse »Accumulator« (Listing 5) verwendet in Zeile 1 die Klasse »Shapes« als Vorlage. Sie beschreibt jedoch kein geometrisches Objekt. Vielmehr akkumuliert sie Werte der Oberflächen und Volumina einer Liste geometrischer Objekte, bei denen es sich um »shapes« genannte Instanzvariablen handelt (Zeile 2).
Listing 5
Definition der Klasse Accumulator
01 Shapes subclass: #Accumulator 02 instanceVariableNames: 'shapes' 03 classVariableNames: '' 04 category: 'Shapes'
Listing 6 zeigt die Methoden der Klasse »Accumulator« . In der Konstruktorfunktion »initialize« (Zeile 1) erzeugt Zeile 3 eine leere Collection in der Instanzvariablen »shapes« . Collections bilden einen Typ vordefinierter und iterierbarer Container-Objekte unter Pharo. Die Methode »set:« (Zeile 5) agiert als Setter-Methode und fügt der Collection ein Element hinzu.
Listing 6
Methoden der Klasse Accumulator
01 initialize 02 super initialize. 03 shapes := OrderedCollection new. 04 05 set: obj 06 shapes add: obj. 07 08 surface 09 | sum | 10 sum := 0. 11 shapes do: [:obj | sum := sum + obj surface]. 12 ^ sum. 13 14 volume 15 ^ shapes inject: 0 into: [:sum :obj | sum + obj volume].
Die Methode »surface« (Zeile 8) erzeugt in Zeile 9 zunächst die lokale Variable »sum« und initialisiert sie in der folgenden Zeile mit dem Wert »0« . Die Methode »do« iteriert in Zeile 11 über die Collection aus der Variablen »shapes« im Stile der Map-Funktion. Zusammen mit jedem Element der Collection ruft »do« den anonymen Funktionsblock »[:obj | sum := sum + obj surface]« auf, der das Element in der lokalen Variablen »obj« (links vom Pipe-Symbol) aufnimmt.
Da Smalltalk über einen lexikalischen Scope verfügt, vergrößert der Ausdruck »sum := sum + obj surface« den Wert der lokalen Variablen »sum« aus Zeile 9 durch Aufruf der Methode »surface« stetig um den Wert der Oberfläche des übergebenen Objekts. Die Methode »volume« (Zeile 14) geht ähnlich vor, verwendet zur Volumenberechnung mit »inject:into:« jedoch eine Methode, die der bekannten Reduce-Funktion entspricht.
Netztauglich
Die Beispielanwendung braucht im Folgenden die Klassen aus dem Package »Shapes« , um die Oberfläche und das Volumen von geometrischen Objekten zu berechnen. Die Spezifikation der Objekte erfolgt in diesem Fall über ein Formular, das der Pharo-Server unter einer URL ausliefert. Die Smalltalk-Bibliothek Seaside [5] erstellt das Formular und wertet es aus. Listing 7 installiert über eine Pharo-Shell die neueste Version in der laufenden Pharo-IDE. Damit Pharo den Code aus dem Listing nach dem Kopieren auch ausführt, muss der User ihn zunächst markieren und anschließend mittels [ALT]+[D] aktivieren.
Listing 7
Installation von Seaside unter Pharo
01 Gofer new url:'http://www.smalltalkhub.com/mc/Seaside/MetacelloConfigurations/main'; 02 package: 'ConfigurationOfSeaside3'; 03 load. 04 ((Smalltalk at: #ConfigurationOfSeaside3) project version: #stable) load.
Listing 8 zeigt die Klassendefinition der Beispielanwendung »WWWShapes« . Sie leitet sich von der Seaside-Klasse »WAComponent« ab. Zeile 2 definiert die drei Instanzvariablen »shapes« , »shape« sowie »dim« .
Listing 8
Definition der Klasse WWWShapes
01 WAComponent subclass: #WWWShapes 02 instanceVariableNames: 'shapes shape dim' 03 classVariableNames: '' 04 category: 'Shapes'
»WAComponent« stellt einen kompletten HTTP-Handler bereit. Trifft eine HTTP-Anfrage eines beliebigen Clients ein, ruft die Klasse die Methode »renderContentOn:« aus Zeile 9 von Listing 9 auf. Die Methode übernimmt das Objekt »html« , um das Webformular zu erzeugen. Zuvor definiert Zeile 10 noch die lokale Variable »group« . Dann generiert »html« in Zeile 11 eine »h1« -Überschrift mit dem Text »Calculate Shape properties« und in Zeile 12 ein klassisches HTML-»form« -Tag.
Listing 9
Methoden der Klasse WWWShapes
01 initialize 02 super initialize. 03 shapes := Accumulator new. 04 05 save 06 shapes set: (shape set: dim). 07 self inform: shapes printAsText. 08 09 renderContentOn: html 10 |group| 11 html heading level: 1; with: 'Calculate Shape properties'. 12 html form: [ 13 group := html radioGroup. 14 group radioButton 15 callback: [ shape := Sphere new]; 16 selected: true. 17 html text: 'Sphere'. 18 group radioButton 19 callback: [ shape := Cube new ]. 20 html text: 'Cube'. 21 html textInput 22 callback: [ :value | dim := value]; 23 value: 0. 24 html submitButton on: #save of: self. 25 ]
Die eingebetteten Elemente landen in einem anonymen Funktionsblock, der sich von Zeile 12 bis Zeile 25 erstreckt. In Zeile 13 erzeugt »html« mit der Methode »radioGroup« ein weiteres Objekt, mit dem die Zeilen 14 bis 16 und 18 bis 19 die beiden Radio-Buttons in Abbildung 1 konstruieren.
Im Aufrufparameter der Methode »callback« stecken anonyme Funktionsblöcke. Die Methode führt sie im Stile von Rückruffunktionen aus, wenn ein Nutzer das Formular absendet. Beim Aufruf erzeugen sie wahlweise ein Objekt vom Typ »Sphere« oder »Cube« und speichern es in der Instanzvariablen »shape« . Analog arbeitet Zeile 22 und speichert dabei den Wert des Eingabefelds (Abbildung 1) in der Instanzvariablen »dim« .
Am Ende verknüpft die Methode »on:of:« in Zeile 24 den Klick auf den Senden-Knopf mit der Methode »save« aus Zeile 5, die damit als letzte Rückruffunktion zum Zuge kommt. Beim ersten Aufruf von »set:« in Zeile 6 setzt die Methode wahlweise den Radius oder die Kantenlänge des zuvor erzeugten Objekts und fügt es dem Akkumulator-Objekt aus Zeile 3 beziehungsweise den »shapes« -Variablen hinzu. Zeile 7 erzeugt aus dem Rückgabewert der Methode »printAsText« eine Antwortseite.
Um die Beispiel-App in Betrieb zu nehmen, starten die folgenden Kommandos eine Instanz des HTTP-Servers Zinc [6], die auf Port 8080 lauscht:
$ ZnZincServerAdaptor startOn: 8080.$ WAAdmin register: WWWShapes asApplication At: 'shapes'.
Zeile 2 registriert in diesem Szenario die Klasse »WWWShapes« über einen Adapter als HTTP-Handler für die URL »http://localhost:8080/shapes« . Ruft er sie auf, gelangt der User zu der eben entwickelten Anwendung.
Fazit
Trotz der ungewohnten Syntax ermöglicht das Smalltalk-Derivat Pharo einen effizienten Entwicklungsprozess, der Entwickler dabei unterstützt, die notwendigen Strukturen mit Code zu füllen. Werkzeuge wie die Paketverwaltung, der Finder oder der Testrunner erleichtern ihm das Leben noch ein wenig mehr. Doch lässt sich der Programmierer voll auf die Benutzerführung von Pharo ein, kann er aus der IDE noch deutlich mehr herausholen [7].
Infos
- Pharo: http://pharo.org
- Smalltalk: http://smalltalk.org
- Pharo unter Linux: http://pharo.org/gnu-linux-installation
- Präzedenz im Kontext von Pharo: http://pharobyexample.org/versions/PBE1-2009-10-28.pdf
- Seaside-Framework: http://www.seaside.st
- Zinc: http://zn.stfx.eu/zn/index.html
- Pharo auf Youtube: https://youtu.be/HOuZyOKa91o









