Dieses Beispiel entstand bei der Vorstellung von Aleph bei Schülern. Die Schüler verband nur der Leistungskurs Mathematik und geringes Interesse an Programmierung. Aleph sollte das Interesse wecken. Als Grund der Ablehnung wurde oft die "nervige Typisierung" und die "komplizierte Syntax" von Sprachen wie C, Scheme oder gar Java angegeben.
Nachdem einige Fähigkeiten von Aleph gezeigt wurden, entstand Interesse an der Fähigkeit beliebige Quellen als Eingabe heranzuziehen. "Dann kann ein Programm ja ein Programm schreiben und gleich ausführen." war die durchaus beabsichtigte Feststellung der Schüler.
Ein "praktisches" Beispiel aus dem IT-Unterricht sollte mit Aleph realisiert werden. Die Schüler wählten die römischen Zahlen, weil praktisch jedem bekannt und sehr beliebt im Informatik-Unterricht.
Nach einiger Zeit wurde als Lösungsvorschlag ein Entwurf präsentiert, der an Einfachheit kaum zu überbieten ist. Die Schüler hatten die Idee das "Wissen" um die Zahlendarstellung zu programmieren und die römische Zahl selbst als Programm zu verwenden.
Römische Zahlen bestehen zwar aus Ziffern, aber manchmal muss subtrahiert statt addiert werden. Wenn aber bestimmte "Zerlegungen" benutzt werden, ist nur noch die Addition erforderlich. Diese Zerlegungen sind das nötige "Wissen" und werden als Anweisungen programmiert.
So werden die Fragment CD und DCCC programmiert als
: CD 400 + ;
: DCCC 800 + ;
So wurden alle Fragmente programmiert. Die römische Zahl ist in irgend einem String und lautet z.B. MVII. Jetzt kommt der geniale Abschnitt der Lösung:
Aus "MVII" wird einfach "M VII". Weil "M" und "VII" als Anweisungen programmiert sind, stellt "M VII" eine ausführbare Sequenz dar.
Die Verblüffung bei den Veranstaltern (eVocation e.V.) war groß. So schnell und so einfach hatten sie noch nie ein Problem in Aleph gelöst. Die Schüler hielten sich gar nicht erst mit Details auf, sondern nutzten Techniken wie "Schönfinkeln" oder "Currying". Es war, als sei ihnen der "Lambda Kalkül" bereits aus der Vorschule bekannt.
Natürlich wurde nicht das ganze hier beschriebene Beispiel erstellt, aber der Ansatz dazu. Die Aufgabe wurde endgültig als gelöst angesehen, als die Methode "replace" korrekt angewendet wurde und die Sequenz
0 "MVII" replace start
das Ergebis 1007 lieferte.
Das folgende Beispiel ist also eine Arbeit von Schülern, die zum ersten mal Kontakt mit Aleph hatten.
Die Schüler hatten an Alles gedacht. Besonders interessant ist dabei die Tatsache, dass allein die Reihenfolge der Zerlegungen die Eindeutigkeit der Sequenz gewährleistet. Um mehrfache Ersetzungen zu vermeiden wurden die Commands der Fragmente mit kleinen Buchstaben versehen (z.B. cd statt CD). Hier die Fragmente als Commands.
: cm 900 + ; : m 1000 + ; : dccc 800 + ; : dcc 700 + ; : dc 600 + ; : cd 400 + ; : d 500 + ; : ccc 300 + ; : cc 200 + ; : xc 90 + ; : c 100 + ; : lxxx 80 + ; : lxx 70 + ; : lx 60 + ; : xl 40 + ; : l 50 + ; : xxx 30 + ; : xx 20 + ; : ix 9 + ; : x 10 + ; : viii 8 + ; : vii 7 + ; : vi 6 + ; : iv 4 + ; : v 5 + ; : iii 3 + ; : ii 2 + ; : i 1 + ; |
Als Initialisierung wird 0 (Null) und danach alle möglichen Fragmente in der richtigen Reihenfolge auf den Stack gelegt. Zuerst wird also "CM" in "cm " gewandelt, sofern vorhanden. In jedem Fall ist diese Reihenfolge wichtig.
: init 0 "I" "II" "III" "V" "IV" "VI" "VII" "VIII" "X" "IX" "XX" "XXX" "L" "XL" "LX" "LXX" "LXXX" "C" "XC" "CC" "CCC" "D" "CD" "DC" "DCC" "DCCC" "M" "CM" // --> KNOWs...0 ; |
Das "Wissen" um die Zerlegung römischer Zahlen liegt jetzt auf dem Stack. Daher auch der Kommentar KNOWs ... 0, der das in kleine Teile zerlegte Wissen zeigen soll.
Die Aufspaltung der römischen Zahl erfolgt in einer Schleife. Sie ist in einer Anweisung namens "dezimal" vorhanden. Dieses Command geht davon aus, dass "Wissen" (KNOWs...0) und römischen Zahl (str) auf dem Stack vorhanden sind.
: dezimal loop swap // str KNOWs...0 --> KNOW/0 str KNOWs...0 lowcase // KNOW/0 str KNOWs...0 --> know/0 KNOW/0 str KNOWs...0 swap 1 nswap // str KNOW/0 know/0 KNOWs...0 --> str KNOWs...0 replace // str KNOW/0 know/0 KNOWs...0 --> str KNOWs...0 ready? leave // str KNOWs...0 --> str KNOWs...0 {or} str 0 endloop ; |
Die verwendeten Anweisungen "lowcase", "replace" und "ready" werden hier in ihren Aufgaben beschrieben, der Code ist ja im Beispiel vorhanden.
lowcase
legt auf dem Stack einen String in kleinen Buchstaben ab, der dem vormals obersten String-Element entspricht. Das Original bleibt dabei erhalten.
replace
ersetzt alle Vorkommen eines Fragments (KNOW/0 es kann auch die Null sein) in dem String mit der römischen Zahl (str) durch die entsprechende Kleinschreibung und ein folgendes Leerzeichen. Das Wissenselement wird dabei in jedem Fall entfernt.
ready?
Überprüft, ob nur noch die 0 (Null) aus dem "Wissen" übrig ist. Dann wird true abgelegt und die Schleife über "leave" verlassen.
Die ursprüngliche Zeichenfolge mit der römischen Zahl besteht jetzt aus kleingeschriebenen Anweisungen und liegt ganz oben auf dem Stack. Gleich darunter liegt die 0 (Null) aus dem abgelegten "Wissen".
Aus der römischen Zahl als String auf dem Stack
"MMVII"
wird
"m m vii" 0
auf dem Stack.
Natürlich wurde den Schülern Hilfe bei der Erstellung einiger Anweisungen gegeben. Die Commands mit direkten Aufrufen von Java-Methoden wurden unterstützt. Es war aber stets so, dass die Schüler sagten was erforderlich ist und die Commands exakt diesen Vorgaben entsprachen.
Der schwierigste Abschnitt ist, die Sequenz ausführen zu lassen. Die pragmatischen Schüler machten Folgendes:
Ausgeben der Sequenz mit . (dot).
Kopieren der Ausgabe in den Eingabebereich.
[Start]-Button anklicken.
Diese Vorgehensweise ist zwar nicht benutzerfreundlich, aber sie zeigt die Korrektheit der Lösung. Die letzte Hilfestellung wurde dann durch die Betreuer gegeben.
Der "reader" für die virtuelle Maschine V2M wird auf dem Stack abgelegt.
Die umgebaute römische Zahl (Sequenz) wird erweitert zu
"m m vi swap "inp" environment store"
Dadurch wird der auf dem Stack abgelegte ursprüngliche "reader" wieder der virtuellen Maschine zugewiesen.
Die Sequenz, also der String ganz oben auf dem Stack, wird über
environment "reader {Object}" call "inp" environment store environment "process { }" call |
als Eingabestrom "inp" festgelegt und über die Methode "process" ausgeführt.
Diese Ergänzungen wurden zwar mit den Schülern zusammen "eingebaut", fanden aber kein sonderliches Interesse mehr. Das Endergebnis ist in der Datei "roman_01.vvm" vorhanden.
Auffällig beim Vorgehen der Teilnehmer war, dass Variablen keine Rolle spielten. Es war beinahe so, als seien sie nicht vorhanden. Der Stack war als zentraler Speicher vorhanden und wurde nach Kräften manipuliert. Ein Grund könnte darin liegen, dass keiner der Teilnehmer über nennenswerte Erfahrung in der Programmierung verfügte. Eine weitere Veranstaltung mit Schülern die Informatik als Leistungskurs belegen sollte durchgeführt werden.
Nach Ende der Veranstaltung wurden die Schüler gebeten ihre Eindrücke von Aleph mitzuteilen. Natürlich freiwillig und per eMail, damit die Antworten ggf. anonym sein können. Hier die Reaktionen (es waren 6 Teilnehmer):
So macht Java Spass!
Die UPN ist lästig. Macht doch auch normale Formeln möglich.
Wer braucht da noch Java?
Endlich eine Sprache in der man das was man ausprobiert auch sofort als Programm benutzten kann. Kein langer Klassenkram und so was. Die Lesbarkeit muss noch besser werden.
Aleph ist flexibel und verfügt über die Möglichkeit jederzeit jedes Objekt einem "Compilat" hinzuzufügen. Das ist einfacher als die Sequenzen erst aus dem Programm heraus zu erstellen und dann auszuführen. Natürlich bleiben die hier gezeigten Möglichkeiten bestehen.