Grundlagen

Universalität

Eine einzige Maschine, die jede andere nachahmen kann, wenn man ihr deren Beschreibung gibt. Turings vielleicht folgenreichste Idee steckt in jedem Computer, jeder JVM und jedem Interpreter – und in erstaunlich vielen Dingen, die nie als Computer gedacht waren.

Lesestand

Eine universelle Turingmaschine bekommt auf ihr Band zwei Dinge: die Beschreibung einer beliebigen anderen Turingmaschine und deren Eingabe. Dann tut sie genau das, was die beschriebene Maschine täte. Man braucht also nicht für jede Aufgabe eine eigene Maschine – eine reicht, und das Programm wird zu Daten.

Genau so funktioniert jeder Computer: Programm und Daten liegen im selben Speicher (Von-Neumann-Architektur). Ein System heißt Turing-vollständig, wenn es eine universelle Maschine simulieren kann. Dafür genügt erstaunlich wenig – weshalb auch Tabellenkalkulationen, Typsysteme und Kartenspiele dazugehören. Und weil Universalität ihren Preis hat, wählt man für Konfigurationen und Datenformate oft bewusst eine schwächere Sprache.

Programme als Daten

Turing zeigte 1936: Man kann eine Turingmaschine als Zeichenkette aufschreiben – Regel für Regel. Und man kann eine Maschine bauen, die eine solche Zeichenkette liest und Schritt für Schritt ausführt. Das Feld links ist so eine Beschreibung, und dieser Simulator ist die universelle Maschine. Ändere die Regeln und schau, was passiert:

Eine universelle Maschine
Programm – eine Regel pro Zeile
Zustand · liest · schreibt · L/R · neuer Zustand. _ ist ein leeres Feld. Die erste Zeile legt den Startzustand fest, der Zustand halt beendet die Maschine.
Dasselbe als ein Wort auf dem Band
▼ Kopf

Zwei Dinge fallen auf. Erstens: Der Simulator selbst ändert sich nie – nur die Daten, die man ihm gibt. Zweitens: Bei der Endlosschleife bricht er nach 2000 Schritten ab. Er kann nicht wissen, ob die Maschine mit Schritt 2001 vielleicht doch angehalten hätte. Dass sich das grundsätzlich nicht vorab entscheiden lässt, ist der Inhalt von G4 Berechenbarkeit.

Von Turing zu von Neumann

Die frühen Rechner wurden für jede Aufgabe neu verkabelt: Beim ENIAC (1945) bedeutete ein neues Programm tagelanges Umstecken. John von Neumann beschrieb 1945 im Entwurf zum EDVAC einen Rechner, der das Programm wie Daten im Speicher hält – die Idee der universellen Maschine, in Röhren gegossen. Ein Programm zu wechseln hieß nun: andere Zahlen in den Speicher laden.

Diese Idee hat sich durch alle Schichten fortgepflanzt. Jede Ebene ist eine universelle Maschine, die die nächste als Daten behandelt:

Dein Java-CodeText – für den Compiler nur Daten
→
BytecodeDaten für die JVM, die ihn interpretiert oder übersetzt
→
JVMselbst ein Programm – Daten für das Betriebssystem
→
MaschinencodeDaten für die CPU, die ihn holt und ausführt
→
CPUdie universelle Maschine aus Silizium

Darauf beruhen auch virtuelle Maschinen, Emulatoren, die alte Spielkonsolen auf dem Handy laufen lassen, und JIT-Compiler, die zur Laufzeit neuen Maschinencode erzeugen und ausführen.

Was „Turing-vollständig“ heißt

Ein System ist Turing-vollständig, wenn es jede Turingmaschine simulieren kann – meist zeigt man das, indem man darin eine einfachere, bekannt universelle Maschine nachbaut. Die Zutaten sind überraschend bescheiden: Speicher, der bei Bedarf wachsen kann, und eine Möglichkeit, abhängig von Daten unterschiedlich weiterzumachen und Dinge zu wiederholen. Deshalb taucht Universalität an unerwarteten Stellen auf. Tippe:

Turing-vollständig oder nicht?

Streng genommen ist kein echter Rechner Turing-vollständig, weil sein Speicher endlich ist. Man sagt es trotzdem, wenn die einzige Grenze der Speicher ist – nicht die Sprache selbst.

Das Prinzip der geringsten Mächtigkeit

Universalität klingt nach einem Vorteil, hat aber einen Preis: Über ein Turing-vollständiges Programm kann man im Allgemeinen nicht vorab sagen, ob es anhält, wie lange es läuft oder was es tun wird – man muss es ausführen. Für Daten und Konfiguration ist das ein Nachteil. Tim Berners-Lee formulierte daraus das Prinzip der geringsten Mächtigkeit: Wähle die schwächste Sprache, die für die Aufgabe ausreicht. Deshalb ist JSON eine reine Datenbeschreibung, deshalb prüft der Linux-Kern eBPF-Programme vor dem Laden darauf, dass sie terminieren, und deshalb sind deklarative Formate wie SQL ohne Rekursion oder Build-Beschreibungen in YAML oft leichter zu prüfen als ein Skript.

Für Entwickler übersetzt

Die JVM ist eine universelle Maschine im Lehrbuchsinn: Programme kommen als Daten herein – als Bytes einer .class-Datei – und werden ausgeführt. Java kann das sogar mit sich selbst:

// Bytecode zur Laufzeit laden – das Programm ist ein byte[]
class AusBytes extends ClassLoader {
    Class<?> lade(String name, byte[] code) {
        return defineClass(name, code, 0, code.length);
    }
}

// Java-Quelltext zur Laufzeit übersetzen – der Compiler als Bibliothek
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
int ergebnis = javac.run(null, null, null, "Generiert.java");

// Das Interpreter-Muster: eine eigene kleine Sprache, deren Programme Objekte sind
sealed interface Ausdruck permits Zahl, Plus, Mal {}
record Zahl(int wert) implements Ausdruck {}
record Plus(Ausdruck l, Ausdruck r) implements Ausdruck {}
record Mal(Ausdruck l, Ausdruck r) implements Ausdruck {}

static int werte(Ausdruck a) {
    return switch (a) {
        case Zahl z -> z.wert();
        case Plus p -> werte(p.l()) + werte(p.r());
        case Mal m  -> werte(m.l()) * werte(m.r());
    };
}

Dieser kleine Interpreter ist übrigens nicht Turing-vollständig – er hat weder Verzweigungen noch Schleifen, jede Auswertung endet garantiert. Genau das kann gewollt sein: Lässt man Nutzer Formeln eingeben, ist eine Sprache, die immer terminiert, viel sicherer als ein eingebetteter Skriptinterpreter.

Wo es im Alltag auftauchtWorauf achten
Skripte in der Anwendung (Groovy, JavaScript-Engines)Laufzeit begrenzen, Zugriffe einschränken – jedes Skript kann endlos laufen
Template-Engines (Thymeleaf, Freemarker)Logik in Templates klein halten; mächtige Templates sind schwer zu prüfen
Build-Skripte (Gradle)sind Programme – deshalb kann ein Build hängen, und Werkzeuge können ihn nur ausführen, nicht vorab analysieren
Deserialisierung von ObjektenDaten sollten Daten bleiben: nur erwartete Typen zulassen, lieber JSON mit festem Schema

Häufige Irrtümer

„Turing-vollständig heißt: leistungsfähig und praktisch.“ – Es heißt nur, dass man im Prinzip alles berechnen kann. Ein Kartenspiel oder der MOV-Befehl sind Turing-vollständig – und völlig unpraktisch.
„Mehr Mächtigkeit ist immer besser.“ – Für Daten, Konfiguration und Nutzereingaben ist eine schwächere Sprache oft die bessere Wahl, weil man sie prüfen kann, ohne sie auszuführen.
„HTML und CSS sind Programmiersprachen, weil man damit viel machen kann.“ – HTML beschreibt Struktur, nicht Berechnung. Über CSS wird gestritten; ohne fortlaufende Nutzerinteraktion gilt es nicht als Turing-vollständig.
„Von Neumann hat das gespeicherte Programm allein erfunden.“ – Die Idee entstand im Team um ENIAC und EDVAC und hat Wurzeln bei Turing. Der Name blieb an von Neumann hängen, weil er den Bericht schrieb.

Verwandte Begriffe