Lisp - der Geheimtipp unter den Programmiersprachen

Der US-amerikanische Entwickler Eric S. Raymond hat einmal gesagt, Lisp sei selbst dann eine lernenswerte Sprache, wenn man sie niemals verwenden werde. Der Erkenntnisgewinn führe dazu, ein allgemein besserer Entwickler zu werden.

Sprachen  –  104 Kommentare
Lisp - Der Geheimtipp unter den Programmiersprachen

Die Softwareentwicklung des vergangenen Vierteljahrhunderts wurde stark von den Programmiersprachen der C-Familie geprägt. Seit dem Erscheinen von C und C++ in den Jahren 1972 und 1985 haben zahlreiche andere Sprachen das Licht der Welt erblickt, die allesamt von ihnen inspiriert sind. Dazu zählen auch Java und C#, die für zahlreiche Entwickler zu den Alltagssprachen schlechthin geworden sind.

Auf den ersten Blick könnte man meinen, dass nahezu alle Sprachen, die seither erschienen sind, mit C und C++ verwandt sind, abgesehen von den unterschiedlichen Dialekten von BASIC, die ebenfalls eine gewisse Reichweite erlangt haben. Der Eindruck trügt allerdings, denn tatsächlich gibt es einen wahren Fundus – nicht nur an anderen Sprachen, sondern vor allem an gänzlich anderen Programmiersprachenkonzepten.

Dazu zählen unter anderem diverse funktionale Sprachen, beispielsweise Haskell und F#. Auch das inzwischen weit verbreitete JavaScript sieht nur äußerlich wie eine Sprache der C-Familie aus, tatsächlich ist es viel stärker von anderen Sprachen beeinflusst. Eine davon ist die 1958 erschienene Sprache Lisp. Der Name ist ein Akronym, das für List Processing steht. Demnach handelt sich bei Lisp offensichtlich um eine Spezialsprache zur Verarbeitung von Listen. Das ist einerseits richtig, verkennt andererseits aber die wahren Qualitäten der Sprache: Das Besondere an Lisp ist nicht, dass es gut mit Listen umgehen kann, sondern die sich daraus ergebenden Konsequenzen.

Paul Graham, Entwickler und Gründer des Risikokapitalgebers Y Combinator, hat zu ebendiesem Thema einen Blogeintrag mit dem Namen "What Made Lisp Different" verfasst, in dem er neun Ideen beschreibt, die Lisp auszeichnen. Einige davon haben im Lauf der Zeit ihren Weg in die gängigen Mainstream-Sprachen gefunden, andere sind nach wie vor exklusiv Lisp vorbehalten.

Als erste Idee führt Paul Graham an, dass Lisp als erste Programmiersprache eine echte if-Anweisung kannte. John McCarthy, der Erfinder von Lisp, berichtet in "LISP prehistory – Summer 1956 through Summer 1958", dass weder das damals verfügbare Fortran noch die darauf aufbauende Fortran List Processing Language (FLPL) die Möglichkeiten besaßen, Bedingungen als Ausdruck zu formulieren:

"While expressions could be handled easily in FLPL [...], it had neither conditional expressions nor recursion, [...]"

Neue Artikelreihe

Es muss nicht immer Java oder C sein: In dieser neuen Reihe stellen unsere Autoren ihre liebsten Programmiersprachen abseits des Mainstreams vor.

Tatsächlich glich die in Fortran enthaltene if-Anweisung einem bedingten Sprung, wie er heute noch in Assembler verwendet wird. McCarthy berichtet weiter, dass für Fortran deshalb rasch die XIF-Funktion entwickelt wurde, die einen von zwei übergebenen Ausdrücken als Rückgabewert zurücklieferte. Da sie jedoch als normale Fortran-Funktion implementiert war, wurden stets beide Ausdrücke ausgewertet:

"The function shortened many programs and made them easier to understand, but it had to be used sparingly, because all three arguments had to be evaluated before XIF was entered, since XIF was called as an ordinary FORTRAN function though written in machine language."

Das änderte sich 1958 mit dem Erscheinen von Lisp, das im heutigen Sinne "richtige" Bedingungen enthielt, die nur einen der beiden übergebenen Ausdrücke auswerten. Das Verhalten ist bis heute geblieben und in jeder modernen Programmiersprache üblich.

Die zweite Idee ist, Funktionen als Datentypen erster Klasse anzusehen, ebenso wie Zeichenketten oder Zahlen. Das bewirkt, dass sich Funktionen wie Daten verarbeiten lassen, da sie als Parameter an andere Funktionen übergeben oder als Rückgabewert von diesen zurückgegeben werden können. Dieser Ansatz bildet beispielsweise die Grundlage für den von Google verwendeten Algorithmus MapReduce, der das effiziente Verarbeiten großer Datenmengen ermöglicht.

Für Entwickler ist dabei ersichtlich, wie die Daten tatsächlich verarbeitet werden. Ist beispielsweise eine Liste von Zahlen zu quadrieren, gibt man lediglich die Funktion an, die die eigentliche Berechnung durchführt. Wie die Liste tatsächlich durchlaufen wird, entscheidet die map-Funktion, mit der sich der Vorgang entsprechend der zur Verfügung stehenden Hardware oder auch im Hinblick auf andere Faktoren optimieren lässt.

Daher wäre es zum Beispiel möglich, die map-Funktion zu parallelisieren und dadurch zu beschleunigen, ohne dass der Entwickler das wissen oder beachten müsste. Genau das beschreibt auch Entwickler Joel Spolsky in seinem Blogeintrag "Can Your Programming Language Do This?", wenn er schreibt:

"And now you understand something I wrote a while ago where I complained about CS students who are never taught anything but Java: Without understanding functional programming, you can't invent MapReduce, the algorithm that makes Google so massively scalable."

Er zieht daraus den Schluss, dass die funktionale Programmierung hilft, das Abstraktionsvermögen einer Sprache zu verbessern, und dass sich Code kompakter sowie besser wiederverwend- und skalierbar schreiben lässt, wenn Funktionen als Bürger erster Klasse angesehen werden:

"Ok. I hope you're convinced, by now, that programming languages with first-class functions let you find more opportunities for abstraction, which means your code is smaller, tighter, more reusable, and more scalable. Lots of Google applications use MapReduce and they all benefit whenever someone optimizes it or fixes bugs."

Die dritte Idee, die in Lisp erstmals umgesetzt wurde, ist die der Rekursion. Das wirkt auf den ersten Blick seltsam, da das Konzept so naheliegend zu sein scheint. Allerdings unterstützt selbst heute nicht jede Sprache das Konzept im vollen Umfang. JavaScript kannte vor ECMAScript 2015 beispielsweise keine Endrekursion, andere Sprachen wie F# und OCaml können zwar mit Rekursion umgehen, man muss sie jedoch explizit anfordern. In F# beispielsweise dient dazu das Schlüsselwort rec: Fehlt es bei der Definition einer Funktion, ist es für sie nicht möglich, sich selbst aufzurufen.

Der Beitrag "Why are functions in Ocaml/F# not recursive by default?" auf Stack Overflow nennt als primären Grund dafür, dass es dadurch möglich ist, Funktionen neu zu definieren, ohne die Zugriffsmöglichkeit auf die ursprüngliche Funktion zu verlieren:

"Functions are not recursive by default in the French CAML family of languages (including OCaml). This choice makes it easy to supercede function (and variable) definitions using let in those languages because you can refer to the previous definition inside the body of a new definition. F# inherited this syntax from OCaml."

Da sich jedes rekursive Problem auch iterativ lösen lässt, schränkt die fehlende Verfügbarkeit des Konzepts Entwickler nur bedingt ein. Es gibt aber zahlreiche Aufgaben, die sich rekursiv einfacher lösen lassen. Man denke dabei an Probleme wie die Berechnung der Fibonacci-Zahlen oder der Fakultät.