Simulator, der zu gegebenem Automaten und Eingabe alle akzeptierenden Rechungen (durch Breitensuche im Berechnungsbaum) sucht.
Geplant: während der Simulation werden logische Formeln getestet, die den (gewünschten) Programmzustand beschreiben.
Der Student schickt seinen Automaten als Haskell-Quelltext an einen Mailserver (procmail), der bei mir (isun11) läuft. Dieser linkt das eingesandte Modul zum Simulatorprogramm, und führt die vorgesehenen Tests aus. Das Resultat wird vom Server an mich und auch an den Studenten geschickt. Von mir aus kann der Student markieren, ob ich das Resultat sehen soll (falls er erst probieren will).
Bei der Aufgabenstellung beachten: gleich die passenden Invarianten mit angeben, also "Schreiben Sie einen Automaten, der genau die Sprache soundso akzeptiert, und bei dem in jeder Konfiguration links vom Kopf stets doppelt soviele Einsen wie ..."
module TuringExample where import Turing ex = Machine { start = "q0" , final = mkSet [ "q2" ] , transitions = turing $ listToFM [ (("q0", ' '), [ ("q1", 'X', R) ]) , (("q1", ' '), [ ("q2", 'Y', R) ]) ] }Das versteht nach kurzem Draufgucken jeder.
Natürlich legt das Verfahren dem Studenten nahe, sich dochmal mit Haskell zu beschäftigen, zum Beispiel, wenn er seine Automaten selbst simulieren will (und nicht zu meinem Mailserver schicken).
Beispiele: Eine Turingmaschine mit einem Band ist Standard.
Wenn der Kopf nur nach rechts fährt (und nichts schreibt), dann ist es ein endlicher Automat.
Nun darf der Kopf beliebig fahren, aber nie schreiben. Dann können immer noch nur reguläre Sprachen akzeptiert werden (Satz von Büchi über Transducer, oder so ähnlich). Ich möchte einen Typkonstruktor "Readonly", den man auf einen Automatentyp anwendet, und dann ohne weitere Programmänderungen nur nicht schreibende Programme erhält.
Was ist, wenn der Kopf beim Rechtsfahren schreiben darf. aber beim Linksfahren löschen muß? Dann haben wir einen Keller.
Wir können uns statt Band auch ein Gitter oder einen Baum oder irgendeinen Graphen denken. Es ist einfach eine Speicherstruktur, und wir müssen uns für jeden Zustand den Speicherinhalt, Kopfposition und Zustand merken. Der Automatentyp wird also mit dem Speicher- (und Index-)Typ parametrisiert.
Man kann dann Aufgaben stellen wie "Schreibe einen Automaten, der erkennt, ob ein Graph einen Kreis enthält". oder "zusammenhängend ist". oder "ob ein Baum vollständig (d. h. alle Blätter gleich tief) ist".
Eine interessante Sache in dieser Richtung sind Automaten mit Pebbles: wie ein normaler read-only-Automat auf irgendeiner Struktur, der aber zusätzlich eine Markierung (Pebble) in der Struktur ablegen darf (und in jedem Zustand fragen, ob auf der aktuellen Position sich eine Markierung befindet). Das entspricht einem Typkonstruktor "Pebble", der zu einem beliebigen Automaten die Pebble-Verwaltung hinzufügt.
Es gibt noch andere Arten der Automatentyp-Kombination: Ich möchte durch wenige Worte aus zwei Einbandmaschinen eine Zweibandmaschine bauen. Typische Anwendung: auf dem einen Band ein nur-rechts-Automat (siehe oben), auf dem anderen ein Keller (siehe oben). Zusammen ergibt das den üblichen Kellerautomaten.
Eine wesentliche Eigenschaft sind die stark polymorphen Typen der Funktionen. Ich kann daran Typkonstruktoren und Konstruktorklassen erläutern sowie aktuell diskutierte Erweiterungen des Haskell-Standards (Multi Parameter Type Classes).
(Alles Weitere, sicher ebenfalls Nötige, wie zum Beispiel Analysis oder imperative Sprachen, dann ab zweitem Semester.)
In der Theorie können wir dann die erlernte funktionale Sprache als ausführbare Spezifikation nutzen für die Begriffe a aus den folgenden Vorlesungen (Beispiel Automaten und Sprachen). Dann sieht alles viel praktischer aus, der Student kann die Definitionen und Sätze tatsächlich hinschreiben, der Compiler prüft auf Typkorrektheit, und schließlich kann man alles sogar ausführen!