Erste Schritte

Zunächst ist Aleph anders als "normale" Programmiersprachen. Es erlaubt schnelle Ergebnisse und bietet ein Höchstmaß an Interaktion. Die aktuellen Forderungen nach Funktionalität und Objektorientierung sind vorhanden. Durch den Einsatz moderner Techniken in der Programmierung auf Basis der Sprache Java ist ein System entstanden, das es dem Anwender praktisch alle erdenklichen Freiheiten lässt. Bis auf eine Ausnahme gibt es keine Regeln, die nicht geändert werden können. Die Ausnahme ist am einfachsten umschieben mit

Der Computer rechnet damit, dass der Programmierer denkt.

Es existiert keine Syntax, keine Grammatik und selbst das Alphabet kann nach eigenen Regeln verwendet werden. Trotzdem sind alle Elemente einer modernen Sprache vorhanden. Angefangen bei der Strukturierung bis zur Programmierung rekursiver Funktionen.

Die Vereinigung dieser, auf den ersten Blick widersprüchlichen, Konzepte wurde erreicht durch die Bereitstellung einer eigenen Maschine deren Sprache Java ist. Weil der Einsatz von Maschinensprache Aufgabe des Compilers ist, kommt der Anwender mit Java nur auf ausdrücklichen Wunsch in Kontakt.

Aleph entlastet den Anwender von syntaktischen Feinheiten, Beachtung der verwendeten Typen, Kapselung bestimmter Sequenzen, Sichtbarkeit von Variablen usw. usw.

Aleph belastet den Anwender mit einer ungeahnten Freiheit im Verfassen von Programmcode. Der Preis dieser Freiheit ist eine Disziplinierung des Denkens nach eigenen Regeln.

Für ein Verständnis der folgenden Abschnitte ist eine bestehende Aleph-Installation hilfreich. Die sehr kurzen, fragmentartigen Beispiele sind dafür gedacht ein erstes "Gefühl" für Aleph zu erhalten. Keinesfalls sagen sie etwas über die Möglichkeiten aus. Dieses Gefühl entscheidet häufig über Akzeptanz oder Ablehnung eines Systems und sollte nicht unterschätzt werden.

Einige Fakten

Für eine Beurteilung sind Fakten unerlässlich. Aleph unterscheidet sich so sehr von herkömmlichen Sprachen, dass die folgenden Punkte erklärt werden müssen:

Es gibt Variablen, sie sind aber nicht erforderlich.

Variablen enthalten nichts, sie sind etwas. Eine Aleph-Variable kann Werte, Sequenzen oder Fragmente von Sequenzen repräsentieren. Sie erfüllen damit die Aufgabe von Substituten, ähnlich wie sie in der Mathematik verwendet werden.

So isteinfachwennist. Genau das machen die Aleph-Variablen möglich. Sie nehmen nicht den Wert auf, sondern den compilierten Code. Weil Aleph Werte nur über Funktionen bereitstellt, kann b auch einfach 5 sein. In diesem Fall ist die Variable b dann eine Identitätsfunktion für den Wert 5. Die Dokumentation enthält ausführliche Hinweise zu Variablen.

Es gibt Typisierung, sie ist aber nicht (immer) erforderlich.

Aleph verwendet automatisch den mächtigeren Datentyp bei Berechnungen. So ist das Ergebnis der Addition eines ganzen und eines gebrochenen Wertes stets gebrochenen Typs. Aleph wählt den jeweils kleinsten Typus, womit z.B. 123 automatisch vom Typ byte ist. Wird jetzt 50 addiert, ist das Ergebnis immer noch vom Typ byte und damit fehlerhaft.

Ein Typ wird jedoch nie in einen weniger mächtigen gewandelt. So ist gewährleistet, dass vom Anwender gewählte Typen beibehalten werden. Die Wahl erfolgt analog dem bekannten typecasting z.B. (float) 5.

Es gibt Schleifen, aber sie enden nie sondern müssen verlassen werden.

Schleifen haben den Aufbau loop ... endloop. Um sie zu verlassen muss ist die Anweisung leave erforderlich. Es ist an eine Bedingung (z. B. a<b) geknüpft und verlässt die Schleife wenn die Bedingung wahr ist.

Der Vorteil besteht in der Freiheit diese Anweisung an jeden beliebigen Ort im Schleifenkörper zu setzen. So sind leicht die gängigen for-, while-, do-, repeat-Konstruktionen zu realisieren.

Aleph Interpreter und Compiler gleichzeitig. Während der Interpretation kann eine Kompilierung erfolgen. Während der Kompilierung eine Interpretation. Natürlich steht eine Anweisung zur Erstellung von Funktionen bereit. Ihre Benutzung ist aber nicht Pflicht, sondern hilfreich.

Eingaben können von jedem Medium kommen, das von Java gelesen werden kann; vom einfachen String bis zum Internet. Die Sicherheit ist über den Java-Security-Manager gewährleistet. Natürlich müssen die Programme in der Sprache von Aleph verfasst sein.

Der Ort von Ein- und Ausgabe kann während des Programmlaufs geändert werden. So ist es möglich, dass ein Programm ein anderes auf ein Medium (Datei, String) schreibt, dieses ausführt und danach mit dem ursprünglichen Medium fortfährt. Hierzu die Beispiele "römische Zahlen" und "GUI".

Die einfache GUI ist nicht Voraussetzung, sondern Hilfestellung. Aleph stellt seine Maschine wie jedes andere Java-Objekt bereit. Deshalb kann es in jede Anwendung integriert werden.

So wie Aleph in Java-Anwendungen integriert werden kann, kann Aleph jedes von Java erreichbare Objekt in eigene Programme integrieren.

Es ist möglich mehrere virtuelle Maschinen gleichzeitig zu benutzen. Ihre Daten (damit auch ihre Funktionen) stehen so allen Instanzen zur Verfügung. Natürlich ist auch eine Vernetzung über getrennte physische Maschinen möglich.

Ausprobieren

Wer bis hierher gelesen hat, wird wohl den Wunsch verspüren, eigene Erfahrungen zu machen. Die GUI ist dabei ein einfaches, aber sehr effizientes Hilfsmittel. in den folgenden Abschnitten wird davon ausgegangen, dass Aleph eingerichtet und, wie im Dokument "Aleph einrichten" beschrieben, gestartet wurde.

Generell gilt: Alles was im Fenster der GUI steht wird nach einem Klick auf den [Start]-Button als "zu verarbeitend" angesehen. Die Eingabe

Mein erster Versuch

führt allerdings zu der Ausgabe:

Was ist das denn? [Mein]

Was ist das denn? [erster]

Was ist das denn? [Versuch]

Ein Klick auf den [Clear]-Button leert das Fenster. Würde eine sinnvolle Eingabe angehängt, wäre der bereits vorhandene Text wieder Anlass zu Fehlermeldungen.

Änderung der Eingabe in

"Mein erster Versuch" .

Führt nun zu der Ausgabe

Mein erster Versuch

Es wurde ein String erzeugt und mit dem '.' (dot) ausgegeben. Der Punkt ist der universelle Ausgabebefehl. Zahlen, Zeichen, praktisch jedes Objekt wird in lesbarer Form ausgegeben.

Der Punkt wurde gewählt, weil er das Ende eines Satzes darstellt. Ein Satz ist eine Wortfolge, die normalerweise eine Reaktion (hier eben die Ausgabe) bewirkt. Alle Eingaben sind zunächst Worte, d.h. Zeichenfolgen ohne Leerzeichen. Ausnahmen sind durch die Verwendung des "-Zeichens möglich.

Für "Anhänger der reinen Lehre":

Das Buch "To Mock a Mocking Bird" oder "Spottdrosseln und Metavögel" von Raymond Smullyan ist eine ideale Einführung in die kombinatorischen Logik. Wer das Buch bereits kennt, kann die Befehle (eigene oder vorhandene) ruhig als "Vögel" interpretieren.

Weil Aleph funktional ist, gelten für Programme die Regeln der Komposition. Die Befehle sind also Kompositionen und ihrerseits (auch) Kombinatoren.

Aleph nutzt die Notation des polnischen Mathematikers Jan Lukasiewicz und kann so auf Klammern verzichten. Eine Alternative wurde von Henk Barendregt für die kombinatorische Logik eingeführt. Hier bedeutet die Sequenz ABC den Ausdruck (AB)C. Die letzte Notation hat ihre Wurzeln im Lambda Kalkül, welcher in der theoretischen Informatik eine entscheidende Rolle spielt. In Aleph soll aber der Bezug zur Praxis bestehen bleiben, weshalb die allgemeinere, erste Notation bevorzugt wird.

Komponieren

Es muss gezeigt werden, dass der Vorgang des Rechnens eine Komposition ist. Hier werden vorhandene Werte zu neuen Werten. Die Regeln sind die Funktionalität des Kombinators. Mathematisch also einfach die Funktion.

Eingabe: 4 5 + .

[Start]-Button anklicĒ©en. Der Punkt (.) liefert das Ergebnis 9 im Ausgabefenster.

Die Addition kombiniert also die beiden Datenelemente zu einem neuen, welches der Summe entspricht.

Um komplexere Berechnungen aus mehreren Operationen durchzuführen müssen die Reihenfolgen von Operatoren und Daten berücksichtigt werden. Mathematisch korrekt handelt es sich um Kompositionen mehrerer Funktionen.

Die Sequenz 3 + 4 * 5 lautet

mathematisch: f(a, g(b, c)) mit a=3, b=4, c=5, +=f und *=g.

in funktionaler Form: +(3 *(4 5))

in Aleph einfach: 3 4 5 * +

Die Sequenz (3 + 4) * 5 wird mit den genannten Substitutionen zu

g(c, f(a, b)) *(5 +(3 4)) oder *(5 +(3 4)) oder 3 4 + 5 *

Eingabe: 1 2 3 .S

[Start]-Button anklicken.

Ausgabe: Stack = [ 3 2 1 ]

Was wurde hier gemacht?

Drei Worte (Zahlen sind auch erst einmal Worte) wurden als Wertobjekte erkannt und zwischengespeichert. Die Speicherung erfolgte nach dem Last_In_First_Out-Prinzip (LIFO oder Stack). Die Anweisung ".S" zeigt den Inhalt an. Das erste Objekt im Speicher wird auch zuerst angezeigt. Die Reihenfolge in der Anzeige ist also umgekehrt der Eingabe.

Der Stack ist also das Speichermedium, in dem die Komposition stattfindet. Es werden also ein paar Kombinatoren benötigt. Vorher aber sollte die Eingabe gelöscht werden, denn die Objekte sind ja gespeichert.

[Clear]-Button anklicken.

Der Eingabebereich ist leer. Jetzt sollen die beiden obersten Elemente auf dem Stack vertauscht werden. Dieser Kombinator ist bereits vorhanden und seine Bezeichnung lautet "swap". Um das Ergebnis in einer neuen Zeile anzuzeigen, ist noch die Ausgabe eines Zeilenvorschubs ('\n') erforderlich. Weil jedes Betriebssystem seine eigene Vorstellung von diesem Zeichen hat, hat Aleph eine allgemeine Lösung.

Eingabe: swap newline . .S

[Start]-Button anklicken.

Ausgabe: Stack = [ 2 3 1 ]

Mit "newline ." wird einfach das '\n'-Zeichen des vorhandenen Betriebssystems ausgegeben. Die Ausgabebefehl ist einfach der '.' (Punkt, dot). Das hat historische Gründe und kann jederzeit geändert werden, wie in der weiteren Dokumentation beschrieben.

Vertauschen geht auch mit tiefer liegenden Objekten. So führt die Eingabe von

1 nswap

zu der Konstellation

Stack = [ 1 2 3 ]

Die Vertauschung mit swap kann über nswap auf den gesamten Stack ausgedehnt werden. Der einzugebende Wert ( 1 ) erhöht die Tiefe der Operation. Diese Erweiterung besitzen auch noch ein paar andere Kombinatoren.

Eingabe: dup .

Ausgabe: 1

Das oberste Element wurde dupliziert und ausgegeben. Der Stack hat immer noch die alten Elemente.

Jetzt ein etwas komplexeres Beispiel mit der erweiterten Version ndup:

Eingabe: 2 ndup 2 ndup 2 ndup . . .

Ausgabe: 123

Elemente können auch entfernt werden, womit die letzten beiden einfachen Kombinatoren besprochen werden.

Eingabe: drop

Stack = [ 2 3 ]

Das oberste Element wird vom Stack entfernt. Es ist weg, steht nicht mehr zur Verfügung, gelöscht.

Eingabe: 1 ndrop

Stack = [ 2 ]

Soweit die Kombinatoren zur Erzeugung bestimmter Konstellationen von Daten. Natürlich ist auch Rechnen eine Komposition, aber hier spielt Funktionalität eine zusätzliche Rolle. Auf diesen Aspekt wird noch eingegangen, vorher soll aber noch die Komposition der Eingabe selbst gezeigt werden.

Programmieren

Ein Programm ist nichts weiter als die Zusammenfassung von Befehlen unter einem Begriff. Es stellt praktisch nur eine Aufzählung dar. So kann das lästige eingeben der Sequenz für Zeilenvorschub und Anzeige des Stacks in einem "Programm" zusammengefasst werden.

: showStack newline . .S ;

Der Name dieses "Programms" lautet showStack. Die Schlüsselanweisung ist der Doppelpunkt (Colon). Es handelt sich um einen Kombinator, der seinerseits aus vorhandenen Kombinatoren aufgebaut ist. In der Dokumentation ist die genaue Beschreibung vorhanden.

Wichtig ist bei der Programmierung das Zusammenspiel von Daten und Funktionen. Es ist muss also noch eine Möglichkeit geben, Anweisungen zu kombinieren. Genau diese Aufgabe übernimmt das Colon.

Compilieren

Der Ablauf ist schnell erklärt. Colon nimmt die nächste Zeichenkette (Wort) von der Eingabe und trägt dieses Wort, als Name einer neuen Anweisung, ins Wörterbuch (Dictionary oder Vokabular) ein.

Jetzt werden alle weiteren Worte aus der Eingabe gelesen und im Wörterbuch gesucht. Ihre Funktionalität ist vom Namen getrennt und kann deshalb als einzelnes Objekt angesehen werden. Dieses "Funktional" wird an eine Liste angehängt.

Der Vorgang wiederholt sich für alle Worte der Eingabe bis das Semikolon gelesen wird. Nun wird die erstellte Liste an das letzte Wort (hier also showStack) im Wörterbuch gebunden.

Natürlich ist der Anwender nicht an diese Konstruktion gebunden. Generell gilt, dass jedes Objekt jederzeit kompiliert werden kann. Aber - nicht alle Funktionen so einfach. Oft ist der Ablauf an Bedingungen geknüpft (if-then-else) oder muss mehrfach durchlaufen werden (loop). Es sind also Anweisungen nötig, die den Ablauf entsprechend der Daten steuern. Für den ersten Kontakt genügt das Wissen um diese Anweisungen; eine genaue Beschreibung ist in der Dokumentation vorhanden.

Objekt oder Funktion

Eine wichtiger Aspekt bei der funktionalen Programmierung ist die Herkunft von Werten. Wie wird der Wert einer Zahl überhaupt bereitgestellt, nachdem die Zahl kompiliert wurde?

Die funktionale Programmierung verlangt eine Funktion für jeden Wert, die objektorientierte Programmierung verlangt ein Wertobjekt. Beide Paradigmen sind in Aleph vorhanden.

Die Eingabe 123 ist zunächst ein Wort. Weil die Zeichenfolge in ein Zahlobjekt (Java) gewandelt werden kann, wird ein solches Objekt erzeugt. Genau genommen handelt es sich um eine Instanz der Klasse "Number". Kompiliert wird dieses Objekt aber nicht selbst. Es wird eine Funktion "n_sym" (Numeric_SYMbol) kompiliert, die das Objekt liefert (auf dem Stack) wenn sie aufgerufen wird.

Es gibt natürlich noch weitere "Werte", die ebenfalls über derartige Funktionen verfügen. So liefert "s_sym" ein String-, "b_sym" ein Boolean-, "c_sym" ein Character-Objekt. Mathematisch handelt es sich stets um Identitätsfunktionen der jeweiligen Wertobjekte.

Auch durch diese Identitätsfunktionen ist die Komposition unvollendeter Funktionen möglich. Diese Funktionen basieren auf dem "Verschmelzungsoperator" des Mathematikers Moses Schönfinkel. In der kombinatorischen Logik kann eine Funktion definiert werden, die einen bestimmten Wert mit einem bereits vorhandenem verknüpft; also z.B. 5 addiert oder 3 subtrahiert.

Soll ein Wert um 1 vermindert werden, kann in Aleph einfach

: decrement 1 - ;

eingegeben werden und fortan liefern Formulierungen wie "5 decrement" das Ergebnis 4.

Wie wichtig diese "Unvollendeten" sind ist im Beispiel zu Umwandlung römischer Zahlen gezeigt. Auch die herkömmliche Programmierung kennt derartige Funktionen, kann sie aber nicht in funktionaler Form bereitstellen. Das wohl geläufigste Beispiel ist "i++" in der Sprache C.

Arithmetische Notation

Bis jetzt wurde immer nur die postfix-Notation (UPN) verwendet. Sie gilt als schwierig zu lesen und deshalb oft abgelehnt. Weil Aleph so wenig Syntax wie möglich enthält, ist UPN zwar die erste Wahl, stellt aber eine grammatikalische Hürde dar. Deshalb gibt es auch die Möglichkeit der arithmetischen oder infix-Notation.

Hier sind die Terme jedoch mit syntaktischen Vorgaben versehen. So müssen Klammern beachtet und Vorzeichen von Operatoren unterschieden werden. Damit auch bei dieser Notation viele Freiheiten bleiben, ist eine Erweiterung vorhanden. Im Beispiel "notations" wird sie ausführlich besprochen.

Weil infix-Notation wegen der vielen versteckten Regeln "nicht normal" ist, geht Aleph den Weg über eine "Dekomposition" des arithmetischen Terms. Der Term muss als String vorliegen und die Wandlung erfolgt dann mit

"4 + 5" infix

Auf dem Stack liegt jetzt eine Sequenz in postfix-Notation.

Stack = [ "4 5 +" ]

Weil Aleph jeden String als Eingabe benutzen kann, ist auch das oberste Element auf dem Stack ausführbar. Weil jedoch der Wunsch nach Kompilierung vorhanden sein kann, muss der Anwender über die weitere Vorgehensweise entscheiden.

Auch eine Übersetzung n die postfix-Notation ist vorhanden. So kann auch die Listenverabeitung von Aleph schnell mit der arithmetischen Notation ausgestattet werden.

Soweit die ersten Schritte. Für die "richtige" Arbeit mit Aleph ist die Dokumentation des Kernels und der Beispiele ein guter Einstieg.