Auf den Spuren von Niklaus Wirth

Modula, zum Zweiten: Modula-2 im Überblick
Kommentare

Niklaus Wirths Produktivität im Bereich der Sprachentwicklung ist nicht zu verachten: Kurz nach der Einführung von Pascal wandte sich der Schweizer einem als Modula bezeichneten Nachfolger zu. Dieser wurde allerdings nicht alt – Modula-2 erschien nur ein Jahr nach der Veröffentlichung der Sprachspezifikation.

Die Arbeit an Modula wurde durch einen Besuch Wirths bei Xerox motiviert: Kurze Zeit später begann man an der ETH Zürich mit der Arbeit an einer Lilith genannten Workstation. Als Programmiersprache sollte naturgemäß eine Eigenentwicklung dienen – das als Lehrsprache vorgesehene Pascal war aus mehreren Gründen ungeeignet. Erstens bot Pascal keine Möglichkeit zur Unterteilung des Codes – Modula sollte diesem Problem durch die Einführung der namensgebenden Module abhelfen. Die zweite Veränderung war die native Implementierung von Nebenläufigkeit, um die Parallelisierung von Aufgaben zu vereinfachen.

Installation

Modula-2 wurde im Jahre 1996 von der ISO standardisiert. Wir wollen in diesem Tutorial auf die GNU-Version zurückgreifen, die für diverse Betriebssysteme gleichermaßen zur Verfügung steht. Die folgenden Schritte erfolgten auf einer Workstation mit Ubuntu 14.04 LTS – andere Betriebssysteme dürften sich bis auf das Deployment analog verhalten. Übrigens: Wer GNU Modula nicht mag, kann beim Unternehmen Excelsior eine IDE für Modula für Windows herunterladen. Weitere Informationen hierzu finden sich auf der Excelsior-Homepage.

Zur Installation öffnen Sie im ersten Schritt die Datei /etc/apt/sources.list (am einfachsten geht das durch Eingabe von sudo gedit /etc/apt/sources.list), und ergänzen Sie sodann um die Deklaration von zwei Repositories:

deb http://floppsie.comp.glam.ac.uk/debian/ jessie main
deb-src http://floppsie.comp.glam.ac.uk/debian/ jessie main

Im nächsten Schritt folgen die übliche Aktualisierung der Paketliste und das Herunterladen der beiden Pakete mit dem Modula-Compiler:

sudo apt-get update
sudo apt-get install gm2-doc gm2

Legen Sie nach der Abarbeitung der Installationsbefehle eine Datei namens helloworld.mod an, die fürs Erste mit folgendem Inhalt bevölkert wird:

MODULE hello ;
FROM StrIO IMPORT WriteString, WriteLn ;
BEGIN
  WriteString('hello world') ; WriteLn
END hello.

Nach der Deklaration des Moduls importieren wir einige Unterstützungsfunktionen aus der StrIO-Bibliothek – spezifischerweise die Ausgabefunktionen WriteString und WriteLn. Danach folgt – ganz im Stil von Pascal – die Einleitung des eigentlichen Programmcodes, der eigentlich nur aufgrund der separaten Nutzung von WriteString zum Ausgeben eines Strings und WriteLn zum Ausgeben von Carriage Return interessant ist. Befehle werden in Modula-2 normalerweise durch ein Semikolon beendet. Beim letzten Statement eines Blocks ist dies allerdings optional, was sich hier an WriteLn feststellen lässt.

Auf der mit gcc 4.8.4 ausgestatteten Workstation des Autors kann sodann ein erster Kompilationsversuch durch Eingabe von gm2 hellomodula.mod erfolgen – bei einem normalen Ubuntu-System scheitert er mit der Fehlermeldung cannot find -lpth. Nach dem Nachladen der Bibliothek mittels sudo apt-get install libpth-dev können wir das Programm sodann folgendermaßen ausführen:

tamhan@tamhan:~/SUSModula2$ ./a.out
hello world

Modula-2 ist in Form eines GCC-Frontends realisiert. Die Universität Glamorgan bietet ein lesenswertes Paper an, das die Vorgehensweise im Detail beschreibt.

Beachten Sie, dass das unsaubere Abschließen des letzten Statements in der Praxis zu Ärger führt. Ein anderer Programmierer könnte das betreffende Snippet nämlich später folgendermaßen erweitern, was ob des fehlenden Semikolons vor WriteLn zu Ärger führt:

BEGIN 
  WriteString('hello world') ; WriteLn
  WriteString('puber') 
END hello.

Modula-2 und Lilith – eine enge Bindung

Die enge Beziehung zwischen Modula-2 und Lilith zeigt sich in den grundlegenden Variablentypen: Da Wirth für seine Workstation einen 16-Bitter avisierte, gibt es zwei Typen namens Integer und Cardinal. Integer beschreibt dabei eine vorzeichenbehaftete Zahl, während Cardinal ohne ein Vorzeichen auskommen muss. Modula-2 unterscheidet sich von C und Co. insofern, als Zuweisungen zwischen vorzeichenbehafteten und nicht vorzeichenbehafteten Variablen nur in sehr begrenztem Rahmen erlaubt sind. Testen wir dies anhand des Codes in Listing 1, der ein Cardinal durch geschicktes Subtrahieren in einen unerlaubten Bereich bringt.

MODULE hello ;

FROM NumberIO IMPORT WriteCard ;
FROM StrIO IMPORT WriteString, WriteLn ;
VAR
myField:CARDINAL;
BEGIN
  myField:=12;
  myField:=myField-22;
  WriteCard(myField, 10);
  WriteLn;
END hello

Auffällig ist hier vor allem die von Pascal bekannte Syntax – die Variablendeklaration, der dedizierte Bereich für die Deklaration von Variablen und der Wirth-typische Zuweisungsoperator sind abermals mit von der Partie. Die Inklusion von WriteCard ist insofern interessant, als die verschiedenen Compilerhersteller im Bereich der Platzierung ihrer Bibliotheksfunktionen mehr oder weniger freie Hand haben. Auf der Workstation des Autors finden sich die diversen Module im Verzeichnis /opt/gm2/lib/gcc/x86_64-linux-gnu/4.7.4/m2/pim/ – zum Durchforsten der diversen .def-Dateien bietet sich beispielsweise der Kommandozeilenbefehl grep an. Ein weiterer netter Unterschied ist, dass der zweite Parameter die für die Ausgabe zu verwendende Menge von Zeichen angibt – manche Runtimes bieten diese Möglichkeit nicht an, was bei der Kompilation von Code zu Beschwerden wegen fehlender Parameter führt.

Wer das vorliegende Programm ausführt, wird auf manchen Runtimes mit einer Fehlermeldung konfrontiert – unter GNU Modula verhält sich die Ausführungsumgebung etwas toleranter, was zum in Abbildung 1 gezeigten Resultat führt. Ein weiteres interessantes Hindernis ist das Codesnippet in Listing 2, das schon bei der Kompilation den in Abbildung 2 gezeigten Fehler produziert.

VAR
myField:CARDINAL;
myInt:INTEGER;
BEGIN

  myField:=12;
  myInt:=12;
  myField:=myField+myInt+2;
. . . 
END hello.

Bei Betrachtung der Fehlermeldung fällt auf, dass Integers und Kardinalzahlen nicht in einer arithmetischen Zuweisung stehen dürfen. Ein Weg zu einer gültigen Formulierung bestünde darin, eine weitere Variable einzuführen, mit der die Zuweisung von Kardinalzahl und Integer vor der Berechnung korrigiert werden kann (Listing 3).

VAR
. . .; 
myConvertor:CARDINAL;
BEGIN
  myField:=12;
  myInt:=12;
  myConvertor:=myInt;
  myField:=myField+myConvertor+2;
  . . .

Die kompilierbare Version des Programms unterscheidet sich von seinem Vorgänger insofern, als wir die Zuweisung von Integer und Cardinal nun in einer eigenen Zeile durchführen: Die Berechnung erfolgt sodann komplett „typenrein“. Alternativ dazu gibt es auch Konversionsfunktionen, deren Nutzung in folgendem Snippet angerissen ist:

BEGIN
  myField:=12;
  myInt:=12;
  myField:=myField+VAL(CARDINAL, myInt)+2;

VAL erlaubt die explizite Konversion zwischen allen primitiven Datentypen der Sprache. Die hier verwendete Variante konvertiert das Integer im Voraus in ein Cardinal, womit die Berechnung ohne den vorher genutzten Zwischenspeicher verwendet werden kann.

Abb. 1: GNU Modula ist bei Unterläufen vergleichsweise kulant – andere Runtimes werfen hier Fehler

Abb. 1: GNU Modula ist bei Unterläufen vergleichsweise kulant – andere Runtimes werfen hier Fehler

Abb. 2: Zuweisungen zwischen „Integer“ und „Cardinal“ sind nur in eingeschränktem Rahmen erlaubt

Abb. 2: Zuweisungen zwischen „Integer“ und „Cardinal“ sind nur in eingeschränktem Rahmen erlaubt

Als weitere primitive Datentypen kennt Modula CHAR, BOOLEAN und REAL. Sie verhalten sich im Großen und Ganzen so, wie man es erwarten würde – interessant sind in diesem Zusammenhang primär die diversen Konversionsfunktionen, die das direkte Makeln zwischen den verschiedenen Datentypen erlauben. Als Beispiel dafür hier TRUNC, die REAL in Integer konvertiert:

BEGIN
  myField:=22.22;
  myInt:=TRUNC(myField);
  . . .

GNU Modula-2 ist ob der Herleitung von GCC mit einigen weiteren Datentypen ausgestattet, die in anderen Dialekten von Modula-2 nicht oder nicht unbedingt unterstützt werden. Tabelle 1 korreliert die Modula-Typen mit ihren aus C bekannten Abbildern.

GNU Modula-2 GNU C
INTEGER int
LONGINT long long int
SHORTINT short int
CARDINAL unsigned int
LONGCARD long long unsigned int
SHORTCARD short unsigned int
BOOLEAN int
REAL double
LONGREAL long double
SHORTREAL float
CHAR char

Tabelle 1: Modula-Typen in Korrelation zu den aus C bekannten Abbildern

Immer wieder, ohne Ende

Die komplette Vorstellung der verwendeten Sprachen ist explizit nicht das Ziel dieser Serie – es geht primär darum, besonders interessante Aspekte vorzustellen. Im Fall von Modula findet sich bei den Schleifen etwas mehr oder weniger Einzigartiges: Neben kopf- und fußgesteuerten Schleifen und einer Implementierung von for gibt es in Modula ein Konstrukt, das das explizite Realisieren einer Endlosschleife ermöglicht. Diese Vorgehensweise ist dem Sicherheitsbedenken Wirths geschuldet: Wenn eine Programmiersprache einen vorgesehenen Weg zur Deklaration einer Endlosschleife aufweist, sind alle anderen Endlosschleifen per Definitionem fehlerhaft. Als kleines Beispiel dafür dient das Programm in Listing 4.

BEGIN
  myIndex:=0;
  LOOP
    WriteInt(myIndex,10);
    IF myIndex = 5 THEN
      WriteLn;
      EXIT;
    ELSIF myIndex = 4 THEN
      WriteString("Unbeliebte Zahl in China!");
    END;
    myIndex := myIndex + 1;
  END;
  WriteString ("End of Loop");
  WriteLn;
END loopdeloop

Nutzer des Endlosschleifenkonstrukts müssen daran denken, die repetitive Abarbeitung der Loop nach einiger Zeit zu beenden. Im Fall unseres Programmbeispiels geschieht das über eine untergeordnete Selektion, die bei Bedarf das Kommando EXIT aufruft und die Schleifenbearbeitung so beendet.

Innerhalb der Selektion findet sich das ELSIF-Kommando: Es handelt sich dabei um eine Implementierung von Else If, die allerdings direkt in der Sprache angelegt ist und nicht wie in C durch „Recycling“ eines Statements entsteht. Die eigentliche Struktur des Codes entspricht sonst – inklusive der Terminierung der Blöcke – dem, was man von Pascal erwarten würde.

Ein weiteres Sicherheitsfeature findet sich in der SELECT-CASE-Struktur, die von Haus aus zum Handling von Wertebereichen gerüstet ist. Als Beispiel dafür dient in Listing 5 ein kleiner Zahlenbewerter, der Temperaturen in „Wohlfühlbereiche“ einteilt – die zur Generierung der Zahlensequenz verwendete FOR-Schleife verhält sich analog zu anderen Sprachen und bedarf hier keiner weiteren Erklärung.

BEGIN
  FOR Index := 1 TO 15 DO
    CASE Index OF
      1..5    : WriteString("Zwischen 1 und 5"); |
      6..12    : WriteString("Zwischen 6 und 12");
    ELSE
      WriteString("Unbekannte Zahl");
    END;  (* des CASE-Falls *)
    WriteLn;
  END
END hello.

Modula-2 divergiert hier von der von Pascal und Co. bekannten und an Sätzen angelehnten Schreibweise. Das Ende der einzelnen Case-Zeilen wird nicht durch END, sondern durch einen vertikalen Strich (|) eingeleitet – für Nutzer anderer Sprachen ungewöhnlich, steht das Zeichen dort doch für die Or-Operation. Die Blöcke ergreifen hierbei sowohl den vorderen als auch den hinteren Grenzfall – Abbildung 3 zeigt die Ausgabe des soeben erzeugten Programms.

Beachten Sie, dass die einzelnen END-Statements normalerweise mit einem Semikolon abgeschlossen werden. Das zur FOR-Schleife gehörende kommt ohne dieses Zeichen aus, weil es das letzte Kommando des Blocks ist und somit vom END-Statement des Blocks mit terminiert wird. Im Sinne der Kompatibilität wäre es allerdings sinnvoll, auch hier ein ; einzufügen – man weiß nie, was bei der Wartung eines Programms an Problemen auftritt.

Abb. 3: Die Test-Cases in Modula-2 verhalten sich ungewöhnlich

Abb. 3: Die Test-Cases in Modula-2 verhalten sich ungewöhnlich

Die Unterteilung von ineinander verschachtelten Blöcken erfolgt in einem klassischen, anhand der END-Tags arbeitenden Parser. Indentierungen – egal, ob per Leerzeichen oder per Tabulator – sind nur für das Auge des Programmierers relevant. Wirths Vorliebe für an natürlicher Sprache orientierte Programmiersprachen zeigt sich derweil bei der Struktur der Kommentare, die – insbesondere im Vergleich zum in C verwendeten /* oder // – wesentlich natürlicher aussehen.

Wichtig ist zudem, dass Modula-2 kein Fallthrough unterstützt. Als Beispiel für das Nichtfunktionieren dient folgender Code, der zur Laufzeit den in Abbildung 4 gezeigten Compilerfehler wirft:

CASE Index OF
  1..5    : WriteString("Zwischen 1 und 5"); |
  6..12   : WriteString("Zwischen 6 und 12");
  13, 14  : WriteString("Spezialfall");
ELSE
Abb. 4: Ein fehlendes | nach einem Case führt zu einer seltsamen Fehlermeldung des Parsers

Abb. 4: Ein fehlendes | nach einem Case führt zu einer seltsamen Fehlermeldung des Parsers

Stattdessen gibt es die Möglichkeit, ein Case-Label für mehrere nicht zusammenhängende Werte scharfzuschalten. Das erfolgt durch Trennung der einzelnen Parameter mit Kommas – ein Beispiel dafür sähe so aus:

CASE Index OF
  1..5    : WriteString("Zwischen 1 und 5"); |
  6..12   : WriteString("Zwischen 6 und 12"); |
  13, 14  : WriteString("Spezialfall");
ELSE
            WriteString("Unbekannte Zahl");
END;  (* des CASE-Falls *)

Das weiter oben angetroffene Semikolonproblem gilt übrigens auch für den vertikalen Strich: Beim letzten Statement eines Case-Blocks ist er nicht unbedingt notwendig. Im Interesse guter Wartbarkeit des Codes – man verzeihe die mantraartige Wiederholung – ist es allerdings empfehlenswert, auch hier sorgfältig vorzugehen und ihn in das Programm einzubinden.

Typ, ändere dich

C bietet mit Unions eine Möglichkeit zur Realisierung von Datentypen an, die teilweise unterschiedliche Inhalte haben: In der Praxis greift ein C-Entwickler eher auf einen void-Pointer zurück, der auf ein zu castendes Feld mit weiteren Informationen verweist. Im Fall von Modula-2 ist diese Vorgehensweise nicht notwendig. Die Sprache bringt den von Pascal gewohnten Record-Datentyp mit, der nun allerdings zweiwertig auftritt. Hinter diesem komplexen Begriff verbirgt sich der Sachverhalt, dass ein Record aus einem als Tag bezeichneten konstanten Teil und einem vom Inhalt des Tags abhängigen variablen Rest bestehen kann.

Zur Vorführung dieses zugegebenermaßen seltsamen Features wollen wir eine Datenstruktur realisieren, die sowohl eine Münze als auch einen gewöhnlichen Edelmetallbarren abbilden kann. Hierbei sind insofern unterschiedliche Parameter erforderlich, als beim Barren das Gewicht von Relevanz ist – bei der Münze interessiert man sich stattdessen eher für die Art und das Erscheinungsjahr. Das Anlegen eines Records mit allen theoretisch benötigten Einträgen wäre heute kein Problem – denken Sie allerdings daran, dass die Lilith mit 65 k 16-Bit-Worten an Arbeitsspeicher auskommen musste und Sparsamkeit somit von besonderer Relevanz war.

Als Erstes müssen wir eine Enumeration deklarieren, die der Runtime das Unterscheiden der beiden Strukturarten ermöglicht:

TYPE   MetallArt = (Barren, Numi);

Im nächsten Schritt folgt die eigentliche Deklaration der Speicherstruktur über den Record-Operator. Unser Record beginnt mit der Deklaration eines Namens, der in allen Permutationen des Eintrags gleichermaßen vorkommt:

MetallRecord = RECORD
  Name   : ARRAY[0..25] OF CHAR;

Interessant wird der darauffolgende Teil. Die Case-Selektion ermöglicht uns das Deklarieren von Elementen, die nur dann zum Einsatz kommen, wenn die Tag-Variable einen bestimmten Wert aufweist:

CASE WhatKind : MetallArt OF
  Barren      : Weight  : CARDINAL;
                ShapeID  : CARDINAL |
  Numi        : Year   : ARRAY[0..8] OF CHAR;    |
  END;
END;

Nach dem Abarbeiten dieser Instruktionen ist der Rekord angelegt: Wir können ihn ab diesem Zeitpunkt als einen eigenen Datentyp verwenden, der zur Deklaration neuer Variablen herangezogen werden kann. Aus didaktischem Interesse wollen wir hierfür einen kleinen Testharnisch realisieren, der mit den Elementen interagiert:

VAR EinBarren, EineMuenze: MetallRecord;
BEGIN
  EinBarren.WhatKind:=Barren;
  EinBarren.Weight:=22;
  WriteCard(EinBarren.Weight, 10);

Wer mit anderen Programmiersprachen – Stichwort C++ und Co. – aufgewachsen ist, fühlt sich mit dieser Syntax im Großen und Ganzen zu Hause.

Aber bitte mit Methode

Als BASIC-Interpreter ihre Goto-Kommandos mit Subs und Funktionen augmentierten, wurde das Erzeugen von übersichtlich strukturierten Programmen einfacher: Ein unendlich langer Codeblock ohne Unterteilungen überfordert die Perzeptionsfähigkeiten des Menschen, der nun mal auf Grafik und nicht auf Text trainiert ist.

Wer Modula-2 zur Erzeugung der namensgebenden Module einsetzen möchte, muss sich im ersten Schritt mit den diversen Möglichkeiten zur Erzeugung von Funktionen befassen. Als erstes Beispiel wollen wir eine Methode erzeugen, die ohne Parameter auskommt und eine aus mehreren Minuszeichen bestehende Linie auf den Bildschirm zeichnet (Listing 6).

PROCEDURE PrintALine;
BEGIN
  WriteString("------------------------------------------");
  WriteLn;
END PrintALine;

VAR MainVar:CARDINAL;
BEGIN
  PrintALine;
  MainVar:=22;
  WriteCard(MainVar, 5);
  WriteLn;
  PrintALine;
END hello.

Interessant ist hier vor allem die von Pascal übernommene Schreibweise. Prozeduren kommen in einem eigenen Begin-End-Block unter, der sich vom Hauptteil des Codes nur durch den PROCEDURE-Header unterscheidet. Der Aufruf einer deklarierten Prozedur kann sodann so erfolgen, wie wir es schon bisher mit den in der Sprache eingebauten Strukturen getan haben.

Im Var-Block des Modulhauptprogramms deklarierte Variablen stehen auch in Untermethoden zur Verfügung. Das lässt sich beispielsweise durch die in Listing 7 gezeigte Variante des Programms vorführen, die den in PrintALine gesetzten Wert weiterleitet.

PROCEDURE PrintALine;
BEGIN
  . . .
  MainVar:=222;
END PrintALine;
VAR MainVar:CARDINAL;
BEGIN
  PrintALine;
  WriteCard(MainVar, 5);
  . . .

Als Nächstes soll die unschöne Kombination aus WriteString und WriteLn beseitigt werden. Dazu ist eine Prozedur erforderlich, die vom Programmierer mit einem Parameter ausgestattet wird (Listing 8).

PROCEDURE WriteStringSmart(x:ARRAY OF CHAR);
BEGIN
  WriteString(x);
  WriteLn;
END WriteStringSmart;
VAR MainVar:CARDINAL;
BEGIN
  WriteStringSmart("Hello World");

Die Realisierung von Funktionen mit Parametern erfolgt im Großen und Ganzen so, wie man es von anderen Sprachen erwarten würde – der Doppelpunkt hinter den Variablennamen gibt den Typ der anzulegenden Variable an. Strings werden in Modula als ARRAY OF CHAR betrachtet – die Bevölkerung mit einer Konstante ist im Hauptteil des Programms demonstriert.

Funktionen und Prozeduren sind nur in den seltensten Fällen als Einbahnstraße ausgelegt: In vielen Fällen wollen Entwickler den in der Methode enthaltenen Wert weiterverarbeiten. Ein schönes Beispiel dafür wäre das Nachimplementieren der in C vorhandenen Prä- und Postfixoperatoren – beginnen wir mit dem einfacheren der beiden, der den inkrementierten Wert zurückliefert:

PROCEDURE Increment(VAR x:CARDINAL):CARDINAL; 
BEGIN 
  x:=x+1;
  RETURN x;
END Increment;

Eine Besonderheit der Sprache ist, dass sie von Haus aus sowohl Pass by Value als auch Pass by Reference ermöglicht. Das hier verwendete Schlüsselwort VAR weist den Compiler darauf hin, dass die Methode den Inhalt des Parameters verändert: Das lässt sich beispielsweise durch folgenden kleinen Testharnisch überprüfen:

VAR MainVar:CARDINAL;
BEGIN
  MainVar:=1;
  MainVar:=Increment(MainVar);
  WriteCard(MainVar, 5);

Modula besteht darauf, dass die Rückgabewerte von Funktionen vom Hauptprogramm abgeerntet werden. Zur Umgehung dieses Problems setzen wir auf eine weitere Variable, deren Wert von unserem Programm nicht weiter verfolgt wird:

VAR MainVar:CARDINAL;
VAR SecondVar:CARDINAL;
BEGIN
  MainVar:=1;
  SecondVar:=Increment(MainVar);
  WriteCard(MainVar, 5);

END hello.

Wer den VAR-Operator vor der Deklaration der Variable entfernt, verwandelt den Parameter in einen klassischen By-Value-Parameter: Das hier demonstrierte „Weiterschreiben“ steht dann nicht mehr zur Verfügung. Interessanter ist da schon die Realisierung des Operators, der den nicht inkrementierten Wert zurückliefert. Hierzu müssen wir im ersten Schritt eine Variable erzeugen, in der wir den zurückzuliefernden Wert zwischenspeichern. Der eigentliche Aufruf des Return-Kommandos kann ja erst nach der Inkrementierung erfolgen – zu diesem Zeitpunkt ist der erhöhte Wert allerdings schon in der Variable gelandet, weshalb ihr Inhalt nicht mehr zurückgegeben werden kann.

Zur Lösung dieses Problems müssen wir die Prozedurendeklaration um einen weiteren Variablenblock ergänzen, der für die Deklaration der diversen lokalen Variablen zuständig ist. Die vollständige Implementierung sieht dann so aus:

PROCEDURE Increment(VAR x:CARDINAL):CARDINAL;
VAR LocalVar:CARDINAL;
BEGIN
  LocalVar:=x;
  x:=x+1;
  RETURN LocalVar;
END Increment;

Anzumerken ist, dass man das Return-Kommando theoretisch auch nach dem Schema „RETURN x-1“ aufbauen könnte – dies unterblieb hier aus didaktischen Gründen, um die Verwendung des var-Blocks besser motivieren zu können.

Prozeduren sind in Modula-2 nicht auf Linearität beschränkt. Wirths Programmiersprache erlaubt Entwicklern das Anlegen von Unterprozeduren: Es handelt sich dabei um „Untermethoden“, die nur aus der sie enthaltenden Hauptmethode aufgerufen werden können. Aufgrund der Einzigartigkeit dieses Features wollen wir auch dafür ein kleines Beispiel realisieren. Der dazu notwendige Code findet sich in Listing 9.

PROCEDURE Increment(VAR x:CARDINAL):CARDINAL;
VAR LocalVar:CARDINAL;
PROCEDURE SayHi();
BEGIN
  WriteString ("Hi!");
END SayHi;
BEGIN
  SayHi();
  LocalVar:=x;
  . . .

Eine Subprozedur unterscheidet sich von normalen Prozeduren durch ihre Sichtbarkeit: Sie steht nur jener Methode zur Verfügung, in deren Korpus ihre Hauptdeklaration liegt. Code „von außen“ könnte die Prozedur nicht nach folgendem Schema aufrufen – wer es trotzdem probiert, wird mit der in Abbildung 5 gezeigten Fehlermeldung bestraft.

Abb. 5: Der Zugriff auf Unterprozeduren von außen ist nicht erlaubt

Abb. 5: Der Zugriff auf Unterprozeduren von außen ist nicht erlaubt

Parallelisierung am Einkernrechner

Da der aus mehreren Bitslice-Chips zusammengesetzte Prozessor der Lilith nicht multitaskingfähig war, mussten die ersten Versionen von Modula ohne Threading-Subsystem auskommen. Da es in der Praxis immer wieder Aufgaben gibt, die auch auf Einkernsystemen von „Suspend and Resume” profitieren, implementierte man stattdessen Koroutinen. Es handelt sich dabei um eine Art „unterbrechbare Funktionen“, die gemeinsam Aufgaben bearbeiten. Eine Koroutine kann dem System dabei erlauben, eine andere Methode vorzuziehen und so selbst in den Hintergrund zu treten. Anders als bei einem klassischen Return bleibt der Inhalt des Stacks bei dieser Operation allerdings erhalten, was dem Entwickler Aufwand beim Hantieren mit dem Systemzustand erspart.

Modula-Tutorials führen dieses Feature gerne anhand einer Implementierung des Spiels „Mensch ärgere Dich nicht“ vor: Wir zeigen an dieser Stelle absichtlich kein Codebeispiel, weil der Entwickler selbst für das Bereitstellen des Speicherplatzes von Stack und Co. verantwortlich ist und dies den Rahmen dieses Artikels komplett sprengen würde. An der Technik interessierten Lesern sei eine Besprechung von Eckart Winkler ans Herz gelegt, die Universität Ulm bietet ebenfalls weitere Informationen zum Thema an.

Modula in der Weiterentwicklung

Wie viele andere Wirth‘sche Entwicklungen erfuhr auch Modula-2 eine rapide Weiterentwicklung, die sich in diverse Unterdistributionen aufteilte. Modula-2+ war eine Gemeinschaftsentwicklung von DEC und Acorn: Die Sprache wurde um Exceptions erweitert, das Koroutinen-Subsystem erfuhr Unterstützung für vollwertige, also wirklich nebeneinander ablaufende Threads.

DEC und Olivetti führten die Entwicklung sodann in Form einer Programmiersprache namens Modula-3 weiter. Sie erweitert das bereits bekannte Modula-2+ um diverse Features der objektorientierten Programmierung: So gibt es unter anderem Unterstützung für Generics. Viele der in Modula-3 implementierten Sprachfeatures wurden von anderen Sprachen – ein Stichwort ist hier Microsofts C# – übernommen; praktische Bedeutung erfuhr die sehr brauchbare Programmiersprache sonst allerdings nicht.

Fazit

Nikolaus Wirth muss – ähnlich wie der AVRO Arrow oder das angelsächsische Unternehmen Psion – zu den großen What Ifs der Informatik gezählt werden. Viele seiner Projekte waren vielversprechend, um im Massenmarkt aus diversen Gründen zu scheitern.

Obwohl es zu Modula-2 noch viel mehr zu erzählen gäbe, wollen wir unsere Beschäftigungen mit Wirth an dieser Stelle beenden – im nächsten Teil der Serie wenden wir uns einer ähnlich innovativen Sprache zu, die zur Ansteuerung des ersten Mehrkern-Microcontrollers der Welt zum Einsatz kam.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -