Im derzeitigen Zustand ist der Sequencer keineswegs komplett und fertig, ich beschreibe aber den Entwurf, und dann werden wir sehen, wie und wann es sich weiter entwickelt. (Die Programmierung ist unter anderm auch einfach eine Zeitfrage. Eine Version ohne GUI läuft bereits, daran kann man auch zeigen, was das GUI noch leisten sollte.)
Nun ist klar, daß das eine eher selten gebrauchte Anwendung ist, aber warum erzähle ich es hier? Es zeigt typische Schritte der Implementierung mittelkleiner Software-Projekte und wie wir dabei spezielle Eigenschaften funktionaler Sprachen nutzen können. Der Arbeitsgang ist folgender
Was sind die Vorteile einer eingebetteten Sprache? Wir erben viele Eigenschaften der Gastsprache. Beispielsweise in Haskell die lokalen Bindungen, Funktionen höherer Ordnung, das Modul- und das Typsystem. Wir müssen uns da also erstens nichts neues ausdenken, zweitens nichts implementieren, und sind drittens sicher, daß und wie es funktioniert.
Hat das überhaupt Nachteile? Wir erben natürlich auch die Implementation (Laufzeitumgebung) der Gastsprache: haben wir einen Haskell-Compiler, wird es eben auch nur kompilierte Musik geben. Haben wir einen Interpreter, gibt es interpretierte Musik. In beiden Fällen jedoch gilt: wollen wir das Programm ändern, müssen wir sein Ausführung stoppen, dann neu kompilieren (oder hugs muß die Module neu laden), und dann neu die Ausführung beginnen. Das ist natürlich grade bei Musik nicht zu vertreten.
Das ist kein inhärenter Nachteil funktionaler Sprachen, die vorliegenden Haskell-Implementationen sind eben so. Unsere Rettung wäre die funktionale Sprache Erlang (TODO: link), die üblicherweise im ständig laufenden Betrieb eingesetzt wird (für Telekommunikations-Steuerung): das Laufzeitsystem ist so gebaut, daß es erstens über viele Rechenknoten verteil ist und zweitens daß "live", d. h. während der Programmausführung Module gewechselt werden können. Das erfordert einige Einschränkungen, aber es geht, und es geht sogar sehr gut (sagt Ericsson). Warum nehmen wir dann nicht Erlang? Es ist ja seit einiger Zeit sogar frei verfügbar. Nun, erstens war das für mich ein bißchen spät, und zweitens hat Erlang ein ganz anderes Typsystem (nämlich eher LISP-ähnlich: jedes Ding hat einen Typ, aber der ist erst zur Laufzeit sicher bekannt). Wenn ich also live Programme modifizieren will, dann muß ich zwangsläufig mir eine Programmiersprache definieren und einen Interpreter und eine Laufzeitumgebung schreiben.
Deswegen setzen wir die Liste der Arbeitsschritte fort:
Was für ein Typsystem wollen wir? Eigentlich wünschen wir uns ja alles sehr nahe an Haskell, weil wir das nun einmal kennen und lieben. Wir schreiben aber andererseits wohl lieber kein komplettes Haskell-System. (Obwohl das gar nicht so abwegig ist, gerade für LISP-Lehrbücher ist ein LISP-Interpreter in LISP ein beliebtes Thema, das sich tatsächlich mit vertretbarem Aufwand implementieren läßt.)
Was könnten wir denn (semantisch) aus Haskell weglassen? Zum Beispiel selbstdefinierte algebraische Datentypen. Wir können aber auch schlecht sagen, alles sein ein Midistrom. Wir brauchen auch einzelne Ereignisse, sicher auch mal Zahlen, auch Booleans (Welchen Typ hat sonst das erste Argument eines if?) Aber Typkonstruktoren möchten wir schon, also Listen, Tupel und Funktionspfeil, und ohne Einschränkung (wie oft man sowas schachteln darf). Verzichtbar sind wahrscheinlich polymorphe Funktionen und damit der Klassen-Mechanismus. Andererseits haben möchten wir gern ein Modulsystem.
Welche Erweiterungen gegenüber Haskell hätten wir denn gern? Ich denke besonders daran, die Funktionsaufrufe dadurch zu erleichtern, daß wir Parameter weglassen dürfen, für die wir vorher passende Defaults festgelegt hatten. (Beispiel die Lautstärke, oder ähnliche Parameter, die sich selten, aber doch manchmal ändern.) Nun kann aber bei einer Funktion aus zwei Gründen ein Parameter fehlen: ersten wollen wir vielleicht stattdessen ein Default, zweitens meinten wir vielleicht tatsächlich eine Funktion höherer Ordnung. Optionale Parameter sind also keine gute Idee. Am sichersten ist es, wenn wir sie nie hinschreiben dürfen, sondern sie vorher (in einem umgebenden Block) deklarieren müssen. Dann muß man nur bei jeder Benutzung einer Variablen entscheiden, ob ihr Wert entlang der statischen oder entlang der dynamischen Aufrufkette gesucht wird. Es gibt bereits Vorschläge, Haskell selbst um genau das zu erweitern (TODO: link)
Was fügen wir zu Haskell hinzu? Für die Default-Variablen gibt es bereits eine vorschlagene Syntax (mit Fragezeichen). Weiterhin müssen wir Sprachbausteine haben, mit denen wir grafische Eingabe-Elemente des GUI bezeichnen. (Knopf, Schieberegler usw.) Da stellt sich die Frage, wo leben denn die dadurch eingegebenen Werte: wenn in einem Funktionstext die Stellung eines Reglers abgefragt wird, und dann zwei Funktionsaufrufe aktiv sind: zeigen wir dann zwei Regler und liest jeder seinen, oder lesen beide den gleichen? Oder wie schaltet man zwischen beiden Möglichkeiten um? Die einfachste Variante wird sein, solche Eingabe-Elemente sind "static" im Sinne von C: sie sind genau einmal da (und alle Benutzer sehen den gleichen, aktuellen Wert).