Sonntag, 6. Juli 2008

Flexible Programmiersprachen

Während sich einige Programmiersprachen wie z.B. SQL auf spezielle Gebiete beschränken, sind die meisten Programmiersprachen, die wir täglich benutzen prinzipiell für alle Probleme einsetzbar. Leute, die ihre theoretischen Kenntnisse deutlich machen wollen, sprechen hier von Turing-vollständig. Dieser Ausdruck basiert auf den Arbeiten des englischen Mathematikers Alan Turing, der schon vor dem Bau der ersten Computer theoretisch gezeigt hat, wie man mit einem Mindestsatz von Operationen alle vorstellbaren mathematischen Probleme lösen kann. Aber nicht jede Programmiersprache macht es dem Benutzer gleich leicht, ein Problem zu formulieren. Einige Programmiersprachen sind optimiert, um bestimmte Probleme zu lösen, während andere Dinge nur schwer umzusetzen sind. So kann man mit FORTRAN relativ gut numerische Algorithmen implementieren. Eine Textverarbeitung oder Datenbank möchte ich aber eher nicht damit schreiben. Andere Programmiersprachen sind leicht zu lernen, wie z.B. BASIC, womit es aber schwer ist, größere Programme lauffähig und wartbar hinzubekommen.

Maschine gegen Logik

Bei der Entwicklung der Programmiersprachen gibt es zwei unterschiedliche Ansätze. Diese entsprechen auch den unterschiedlichen Abstammungen der Informatiklehrstühle der Universitäten. Bevor sich die Informatik als eigenständige Disziplin etabliert hatte, waren die späteren Informatiker entweder Elektroingenieure oder Mathematiker. Demnach gab es zwei unterschiedliche Herangehensweisen. Die einen hatten ihre Schaltungen und versuchten damit etwas sinnvolles anzufangen. Die anderen hatten ihre Logik, Lambdacalculus, Kategorientheorie etc. und versuchten diese Theorien irgendwie auf die Maschinen abzubilden. Das sieht man auch den Programmiersprachen bis heute an. Zum einen gibt es da die Sprachen wie C. C stammt in direkter Linie von der Maschinensprache ab. Ich bezeichne es gerne als prozessorarchitekturunabhängigen Assembler. Damit lassen sich recht einfach Programme schreiben, die sehr effizient mit der Maschne umgehen. Auf der anderen Seite gibt es Sprachen wie LISP und Smalltalk. Diese beruhen auf wenigen, aber mathematisch fundierten Grundprinzipien. Die Effizienz kann zwar auch von Programmierer beeinflußt werden, liegt aber zuerst einmal bei dem Implementierer der Compiler und der Laufzeitumgebung.

Kontrollstrukturen

Wichtige Elemente von Programmiersprachen, sind zum einen Funktionen und zum anderen Kontrollstrukturen. In der Regel wird eine Programmiersprache mit einer Bibliothek von Funktionen ausgeliefert. Der Benutzer kann aber - und muß, wenn er etwas sinnvolles bewerkstelligen will - aber eigene Funktionen hinzufügen. Bei den Kontrollstrukturen ist das aber bei Sprachen wie C oder auch Java nicht möglich. Wenn es noch keine foreach-Schleife gibt, um über eine Sammlung von Objekten zu iterieren, so muß man halt auf eine neue Version der Sprache warten, die dieses Kontrollstruktur hinzufügt.

Bei Smalltalk z.B. ist das aber anders. Diese Sprache ist das extremste Beispiel für das "alles ist ein Objekt"-Prinzip. Kontrollstrukturen basieren hier auch auf Objekten. Eine If-Else-Struktur basiert auf Objekten vom Typ Boolean. Dieser Typ besitzt Methoden IfTrue: oder IfElse:. Diesen Methoden werden Codeblöcke als Argumente mitgegeben, die abhängig vondem Wert des Objekts ausgeführt werden. Bei den Codeblöcken handelt es sich um dasselbe, was wir letzte Woche als Closure kennengelernt haben.

Warum geht ähnliches nicht mit Java? Wenn wir versuchen, ein Commandobjekt zu bauen, das ein Attribut des Typs bool besitzt und zwei mit der Signatur eines Commandobjekts, so kann man eine Verknüpfung erreichen, die in eine If-Else-Verzweigung simuliert. Dies wird gelegentlich auch so implementiert, wenn man den Programmablauf zur Laufzeit zusammensetzen muß.

Das Problem hier ist, daß das alles extrem umständlich wird, da man den Objekten alles explizit mitgeben muß, was sie benötigen. In einem normalen If-Else-Konstrukt sind aber alle Elemente des Kontextes, in dem das Konstrukt exisitert verfügbar, ohne das sie extra hinkopiert werden müßten.

Wenn ich Closures in meiner Programmiersprache unterstütze, so kann die Sprache jederzeit um neue Kontrollstrukturen erweitert werden. Recht verbreitet sind hier individuelle Schleifen, die über eine Sammlung von Objekten iterieren und hierbei die innere Struktur der Sammlung kennen. Siehe z.B. folgenden Ruby-Code:

a = [ "a", "b", "c", "d" ]
a.collect {|x| x + "!" } #=> ["a!", "b!", "c!", "d!"]

Flexibilität für alle?

Wenn man die Sprache so leicht erweitern kann, so kann man sehr kompakten und übersichtlichen Code schreiben. Wie alles ist aber auch dies nicht umsonst zu haben. Leute, die von der C-Denkschule kommen, müssen zuerst umlernen. Außerdem gilt hier noch mehr als in anderen Fällen, daß es immer schwerer wird, neue Mitarbeiter für ein bestehendes Projekt einzuarbeiten, da zu Beginn die allgemeinen Kenntnisse der Programmiersprache nur eine Grundlage geben, aber bei weitem nicht ausreichen, den Programmcode lesen zu können.
Insbesondere LISP-Programmierer sind berüchtigt dafür, daß sie für jedes Projekt praktisch eine eigene Programmiersprache entwickeln, die auf Lisp aufbaut und die typische Klammerstruktur besitzt, aber über ganz eigene Kontrollstrukturen verfügt.
Der Vorteil aber ist, daß, nachdem die Grundlagen geschaffen sind, die Arbeit deutlich vereinfacht wird und die Anzahl von Fehlern dadurch zurückgeht.
Der Trend in den modernen Programmiersprachen geht eindeutig in diese Richtung. Schon C# 3.0, Ruby und Python bieten viele Möglichkeiten, ganz zu schweigen von aufkommenden Sprachen wie Scala und F#.

Keine Kommentare: