Loading...

RACHE DER NERDS

Original

Mai 2002

"Wir waren hinter den C++-Programmierern her. Wir haben es geschafft, eine Menge von ihnen etwa zur Hälfte nach Lisp zu ziehen."

  • Guy Steele, Mitautor der Java-Spezifikation

In der Softwarebranche gibt es einen andauernden Kampf zwischen den spitzköpfigen Akademikern und einer anderen ebenso formidablen Kraft, den spitzköpfigen Chefs. Jeder weiß, wer der spitzköpfige Chef ist, oder? Ich denke, die meisten Menschen in der Technologiewelt erkennen nicht nur diese Comicfigur, sondern kennen auch die tatsächliche Person in ihrem Unternehmen, auf der sie basiert.

Der spitzköpfige Chef kombiniert auf wundersame Weise zwei Eigenschaften, die einzeln häufig vorkommen, aber selten zusammen zu sehen sind: (a) er weiß überhaupt nichts über Technologie, und (b) er hat sehr starke Meinungen darüber.

Nehmen wir zum Beispiel an, Sie müssen eine Software schreiben. Der spitzköpfige Chef hat keine Ahnung, wie diese Software funktionieren muss, und kann eine Programmiersprache nicht von der anderen unterscheiden, und trotzdem weiß er, in welcher Sprache Sie sie schreiben sollten. Genau. Er denkt, Sie sollten sie in Java schreiben.

Warum denkt er das? Lass uns einen Blick in das Gehirn des spitzköpfigen Chefs werfen. Was er denkt, ist in etwa Folgendes: Java ist ein Standard. Ich weiß, es muss ein Standard sein, denn ich lese ständig darüber in der Presse. Da es ein Standard ist, werde ich dafür nicht in Schwierigkeiten geraten. Und das bedeutet auch, dass es immer viele Java-Programmierer geben wird, so dass ich, wenn die Programmierer, die jetzt für mich arbeiten, wie Programmierer, die für mich arbeiten, mysteriöserweise immer tun, kündigen, sie leicht ersetzen kann.

Nun, das klingt nicht ganz unvernünftig. Aber es basiert alles auf einer unausgesprochenen Annahme, und diese Annahme erweist sich als falsch. Der spitzköpfige Chef glaubt, dass alle Programmiersprachen im Grunde gleichwertig sind. Wenn das wahr wäre, läge er richtig. Wenn Sprachen alle gleichwertig sind, sicher, verwenden Sie einfach die, die alle anderen auch verwenden.

Aber alle Sprachen sind nicht gleichwertig, und ich denke, ich kann Ihnen das beweisen, ohne auch nur auf die Unterschiede zwischen ihnen einzugehen. Wenn Sie den spitzköpfigen Chef 1992 gefragt hätten, in welcher Sprache Software geschrieben werden sollte, hätte er genauso zögerungslos geantwortet wie heute. Software sollte in C++ geschrieben werden. Aber wenn Sprachen alle gleichwertig sind, warum sollte sich die Meinung des spitzköpfigen Chefs je ändern? Tatsächlich, warum hätten die Entwickler von Java sich überhaupt die Mühe gemacht, eine neue Sprache zu schaffen?

Vermutlich, wenn Sie eine neue Sprache schaffen, ist es, weil Sie denken, dass sie in irgendeiner Weise besser ist als das, was die Leute schon hatten. Und tatsächlich macht Gosling in dem ersten Java-Whitepaper deutlich, dass Java entwickelt wurde, um einige Probleme mit C++ zu beheben. Also da haben Sie es: Sprachen sind nicht alle gleichwertig. Wenn Sie dem Pfad durch das Gehirn des spitzköpfigen Chefs zu Java und dann zurück durch die Geschichte von Java zu seinen Ursprüngen folgen, landen Sie mit einer Idee, die der Annahme, von der Sie ausgegangen sind, widerspricht.

Wer hat also Recht? James Gosling oder der spitzköpfige Chef? Nicht überraschend, Gosling hat Recht. Einige Sprachen sind für bestimmte Probleme besser als andere. Und wissen Sie, das wirft einige interessante Fragen auf. Java wurde entwickelt, um für bestimmte Probleme besser zu sein als C++. Welche Probleme? Wann ist Java besser und wann C++? Gibt es Situationen, in denen andere Sprachen besser sind als eine von beiden?

Sobald Sie diese Frage in Betracht ziehen, haben Sie eine echte Büchse der Pandora geöffnet. Wenn der spitzköpfige Chef sich das Problem in seiner vollen Komplexität ansehen müsste, würde sein Gehirn explodieren. Solange er alle Sprachen als gleichwertig betrachtet, muss er nur die wählen, die den meisten Schwung zu haben scheint, und da das mehr eine Frage der Mode als der Technologie ist, kann er die richtige Antwort wahrscheinlich sogar selbst finden. Aber wenn Sprachen variieren, muss er plötzlich zwei simultane Gleichungen lösen, um einen optimalen Ausgleich zwischen zwei Dingen zu finden, von denen er nichts weiß: die relative Eignung der zwanzig oder so führenden Sprachen für das Problem, das er lösen muss, und die Chancen, Programmierer, Bibliotheken usw. für jede zu finden. Wenn das auf der anderen Seite der Tür wartet, ist es kein Wunder, dass der spitzköpfige Chef sie nicht öffnen möchte.

Der Nachteil des Glaubens, dass alle Programmiersprachen gleichwertig sind, ist, dass es nicht wahr ist. Aber der Vorteil ist, dass es Ihr Leben viel einfacher macht. Und ich denke, das ist der Hauptgrund, warum diese Idee so weit verbreitet ist. Es ist eine bequeme Idee.

Wir wissen, dass Java ziemlich gut sein muss, weil es die coole, neue Programmiersprache ist. Oder etwa nicht? Wenn Sie sich die Welt der Programmiersprachen aus der Ferne ansehen, sieht es so aus, als sei Java das Neueste. (Aus der Ferne sehen Sie nur das große, blinkende Werbeplakat, das von Sun bezahlt wurde.) Aber wenn Sie sich diese Welt genauer ansehen, stellen Sie fest, dass es Abstufungen von Coolness gibt. Innerhalb der Hacker-Subkultur gibt es eine andere Sprache namens Perl, die als viel cooler als Java gilt. Slashdot zum Beispiel wird von Perl generiert. Ich glaube nicht, dass Sie diese Leute Java Server Pages verwenden sehen würden. Aber es gibt eine andere, neuere Sprache namens Python, deren Nutzer dazu neigen, auf Perl herabzublicken, und mehr wartet in den Startlöchern.

Wenn Sie sich diese Sprachen in der Reihenfolge Java, Perl, Python ansehen, fällt Ihnen ein interessantes Muster auf. Zumindest fällt Ihnen dieses Muster auf, wenn Sie ein Lisp-Hacker sind. Jede ist schrittweise mehr wie Lisp. Python kopiert sogar Funktionen, die viele Lisp-Hacker als Fehler ansehen. Sie könnten einfache Lisp-Programme Zeile für Zeile in Python übersetzen. Es ist 2002, und Programmiersprachen haben Lisp von 1958 fast eingeholt.

Schritt halten mit der Mathematik

Was ich meine ist, dass Lisp 1958 von John McCarthy entdeckt wurde und populäre Programmiersprachen erst jetzt die Ideen aufholen, die er damals entwickelt hat.

Wie kann das sein? Ist Computertechnologie nicht etwas, das sich sehr schnell verändert? Ich meine, 1958 waren Computer riesige Kühlschrankkolosse mit der Rechenleistung einer Armbanduhr. Wie könnte irgendeine Technologie, die so alt ist, überhaupt noch relevant sein, geschweige denn der neuesten Entwicklung überlegen?

Ich werde es Ihnen sagen. Der Grund ist, dass Lisp nicht wirklich als Programmiersprache konzipiert war, zumindest nicht in dem Sinne, wie wir es heute verstehen. Was wir unter einer Programmiersprache verstehen, ist etwas, das wir verwenden, um einem Computer zu sagen, was er tun soll. McCarthy beabsichtigte zwar schließlich, eine Programmiersprache in diesem Sinne zu entwickeln, aber das Lisp, das wir letztendlich bekommen haben, basierte auf etwas Separatem, das er als theoretische Übung durchführte - ein Versuch, eine bequemere Alternative zur Turing-Maschine zu definieren. Wie McCarthy später sagte,

Eine andere Möglichkeit, zu zeigen, dass Lisp ordentlicher als Turing-Maschinen war, bestand darin, eine universelle Lisp-Funktion zu schreiben und zu zeigen, dass sie kürzer und verständlicher ist als die Beschreibung einer universellen Turing-Maschine. Dies war die Lisp-Funktion eval, die den Wert eines Lisp-Ausdrucks berechnet... Das Schreiben von eval erforderte die Erfindung einer Notation, die Lisp-Funktionen als Lisp-Daten darstellt, und eine solche Notation wurde für die Zwecke des Papiers entwickelt, ohne dass daran gedacht wurde, sie zur Darstellung von Lisp-Programmen in der Praxis zu verwenden.

Was dann passierte, war, dass Steve Russell, einer von McCarthys Doktoranden, irgendwann Ende 1958 diese Definition von eval ansah und erkannte, dass, wenn er sie in Maschinensprache übersetzte, das Ergebnis ein Lisp-Interpreter sein würde.

Das war zu der Zeit eine große Überraschung. Hier ist, was McCarthy später in einem Interview dazu sagte:

Steve Russell sagte, schau, warum programmiere ich dieses eval nicht..., und ich sagte zu ihm, ho, ho, du verwechselst Theorie mit Praxis, dieses eval ist zum Lesen gedacht, nicht zum Berechnen. Aber er ging einfach daran und tat es. Das heißt, er kompilierte das eval aus meinem Papier in [IBM] 704-Maschinencode, behoben Fehler, und dann warb er damit als einem Lisp-Interpreter, was es sicherlich war. Also hatte Lisp zu diesem Zeitpunkt im Wesentlichen die Form, die es heute hat...

Plötzlich, innerhalb von Wochen, fand McCarthy, dass seine theoretische Übung in eine tatsächliche Programmiersprache verwandelt worden war - und eine mächtigere, als er beabsichtigt hatte.

Die kurze Erklärung dafür, warum diese Sprache aus den 1950er Jahren nicht veraltet ist, ist, dass es keine Technologie, sondern Mathematik war, und Mathematik wird nicht alt. Das Richtige, womit man Lisp vergleichen sollte, ist nicht die Hardware der 1950er Jahre, sondern zum Beispiel der Quicksort-Algorithmus, der 1960 entdeckt wurde und immer noch der schnellste allgemeine Sortieralgorithmus ist.

Es gibt noch eine andere Sprache, die aus den 1950er Jahren stammt und noch überlebt, Fortran, und sie repräsentiert den entgegengesetzten Ansatz zum Sprachdesign. Lisp war ein theoretisches Konstrukt, das unerwartet in eine Programmiersprache umgewandelt wurde. Fortran wurde absichtlich als Programmiersprache entwickelt, aber eine, die wir heute als sehr niedrigstufig betrachten würden.

Fortran I, die Sprache, die 1956 entwickelt wurde, war ein sehr anderes Tier als das heutige Fortran. Fortran I war im Grunde Assemblersprache mit Mathematik. In mancher Hinsicht war es weniger leistungsfähig als neuere Assemblersprachen; es gab zum Beispiel keine Unterprogramme, nur Sprünge. Das heutige Fortran ist jetzt argumentativ näher an Lisp als an Fortran I.

Lisp und Fortran waren die Stämme zweier getrennter Evolutionsbäume, einer verwurzelt in der Mathematik und einer in der Maschinenarchitektur. Diese beiden Bäume haben sich seitdem immer weiter angenähert. Lisp startete mächtig und wurde in den nächsten zwanzig Jahren schnell. Die sogenannten Mainstream-Sprachen starteten schnell und wurden in den nächsten vierzig Jahren allmählich mächtiger, bis die fortgeschrittensten von ihnen Lisp ziemlich nahe kommen. Nah, aber es fehlen ihnen immer noch ein paar Dinge...

Was Lisp anders machte

Als Lisp zum ersten Mal entwickelt wurde, verkörperte es neun neue Ideen. Einige davon nehmen wir heute als selbstverständlich hin, andere finden sich nur in fortgeschritteneren Sprachen, und zwei sind immer noch einzigartig für Lisp. Die neun Ideen sind in der Reihenfolge ihrer Übernahme durch den Mainstream:

Bedingte Anweisungen. Eine bedingte Anweisung ist eine if-then-else-Konstruktion. Wir nehmen sie heute als selbstverständlich hin, aber Fortran I hatte sie nicht. Es hatte nur einen bedingten Sprung, der eng an die zugrunde liegende Maschineninstruction angelehnt war.

Einen Funktionstyp. In Lisp sind Funktionen ein Datentyp wie ganze Zahlen oder Zeichenketten. Sie haben eine wörtliche Darstellung, können in Variablen gespeichert, als Argumente übergeben usw. werden.

Rekursion. Lisp war die erste Programmiersprache, die sie unterstützte.

Dynamische Typisierung. In Lisp sind alle Variablen effektiv Zeiger. Werte haben Typen, nicht Variablen, und das Zuweisen oder Binden von Variablen bedeutet das Kopieren von Zeigern, nicht dessen, worauf sie zeigen.

Garbage-Collection.

Programme, die aus Ausdrücken bestehen. Lisp-Programme sind Bäume von Ausdrücken, von denen jeder einen Wert zurückgibt. Das steht im Gegensatz zu Fortran und den meisten nachfolgenden Sprachen, die zwischen Ausdrücken und Anweisungen unterscheiden.

Es war naheliegend, diese Unterscheidung in Fortran I zu haben, da man dort Anweisungen nicht verschachteln konnte. Und so, während man Ausdrücke für die Mathematik brauchte, gab es keinen Grund, irgendetwas anderes einen Wert zurückgeben zu lassen, da nichts darauf warten konnte.

Diese Einschränkung verschwand mit dem Aufkommen blockstrukturierter Sprachen, aber da war es zu spät. Der Unterschied zwischen Ausdrücken und Anweisungen hatte sich bereits etabliert. Er verbreitete sich von Fortran in Algol und dann in deren Nachkommen.

Einen Symboltyp. Symbole sind effektiv Zeiger auf in einer Hashtabelle gespeicherte Zeichenketten. So kann man Gleichheit durch Zeigervergleich testen, anstatt jedes Zeichen zu vergleichen.

Eine Notation für Code, die Bäume von Symbolen und Konstanten verwendet.

Die ganze Sprache ist die ganze Zeit da. Es gibt keinen wirklichen Unterschied zwischen Lese-, Kompilier- und Laufzeit. Man kann Code kompilieren oder ausführen, während man liest, lesen oder ausführen, während man kompiliert, und lesen oder kompilieren zur Laufzeit.

Das Ausführen von Code zur Lesezeit ermöglicht es den Benutzern, die Syntax von Lisp umzuprogrammieren; das Ausführen von Code zur Kompilierzeit ist die Grundlage für Makros; das Kompilieren zur Laufzeit ist die Grundlage für den Einsatz von Lisp als Erweiterungssprache in Programmen wie Emacs; und das Lesen zur Laufzeit ermöglicht es Programmen, über s-Ausdrücke zu kommunizieren, eine Idee, die kürzlich als XML wiederentdeckt wurde.

Als Lisp zum ersten Mal auftauchte, waren diese Ideen weit entfernt von der üblichen Programmierpra xis, die weitgehend durch die Hardware der späten 1950er Jahre bestimmt war. Im Laufe der Zeit hat sich die Standardsprache, verkörpert in einer Reihe von populären Sprachen, allmählich in Richtung Lisp entwickelt. Die Ideen 1-5 sind jetzt weit verbreitet. Nummer 6 beginnt, im Mainstream aufzutauchen. Python hat eine Form von 7, obwohl es dafür keine Syntax zu geben scheint.

Was die Nummer 8 angeht, so ist dies möglicherweise die interessanteste der Menge. Die Ideen 8 und 9 wurden nur durch Zufall Teil von Lisp, weil Steve Russell etwas implementiert hat, das McCarthy nie zu implementieren beabsichtigt hatte. Und doch erweisen sich diese Ideen als verantwortlich für das seltsame Aussehen von Lisp und seine charakteristischsten Merkmale. Lisp sieht nicht so sehr deshalb seltsam aus, weil es eine seltsame Syntax hat, sondern weil es keine Syntax hat; Sie drücken Programme direkt in den Parserbäumen aus, die hinter den Kulissen entstehen, wenn andere Sprachen geparst werden, und diese Bäume bestehen aus Listen, die Lisp-Datenstrukturen sind.

Es stellt sich heraus, dass es ein sehr leistungsfähiges Merkmal ist, die Sprache in ihren eigenen Datenstrukturen auszudrücken. Die Ideen 8 und 9 zusammen bedeuten, dass Sie Programme schreiben können, die Programme schreiben. Das mag wie eine bizarre Idee klingen, ist aber in Lisp ein alltägliches Ding. Der gängigste Weg, dies zu tun, ist mit etwas, das als Makro bezeichnet wird.

Der Begriff "Makro" bedeutet in Lisp nicht das, was er in anderen Sprachen bedeutet. Ein Lisp-Makro kann von einer Abkürzung bis zu einem Compiler für eine neue Sprache reichen. Wenn Sie Lisp wirklich verstehen oder Ihren Programmhorizont erweitern möchten, erfahren Sie mehr über Makros.

Makros (im Lisp-Sinne) sind meines Wissens nach nach wie vor einzigartig für Lisp. Dies liegt zum Teil daran, dass man, um Makros zu haben, seine Sprache wahrscheinlich genauso seltsam machen muss wie Lisp. Es kann auch daran liegen, dass man, wenn man diese letzte Steigerung der Leistungsfähigkeit hinzufügt, nicht mehr behaupten kann, eine neue Sprache erfunden zu haben, sondern nur einen neuen Dialekt von Lisp.

Ich erwähne dies hauptsächlich als Scherz, aber es ist durchaus wahr. Wenn Sie eine Sprache definieren, die car, cdr, cons, quote, cond, atom, eq und eine Notation für als Listen ausgedrückte Funktionen hat, dann können Sie den Rest von Lisp daraus aufbauen. Das ist in der Tat die definitorische Eigenschaft von Lisp: Um dies zu ermöglichen, gab McCarthy Lisp die Form, die es hat.

Wo Sprachen wichtig sind

Angenommen also, Lisp stellt eine Art Grenze dar, der sich die Mainstream-Sprachen asymptotisch nähern - bedeutet das, dass Sie es tatsächlich verwenden sollten, um Software zu schreiben? Wie viel verlieren Sie, wenn Sie eine weniger leistungsfähige Sprache verwenden? Ist es manchmal nicht klüger, nicht ganz an der Spitze der Innovation zu sein? Und ist Popularität nicht bis zu einem gewissen Grad ihre eigene Rechtfertigung? Hat der Pointy-Haired Boss zum Beispiel nicht Recht, eine Sprache verwenden zu wollen, für die er leicht Programmierer finden kann?

Es gibt natürlich Projekte, bei denen die Wahl der Programmiersprache nicht viel ausmacht. In der Regel gilt: Je anspruchsvoller die Anwendung, desto mehr Hebel bekommen Sie durch den Einsatz einer leistungsfähigen Sprache. Aber viele Projekte sind überhaupt nicht anspruchsvoll. Der Großteil der Programmierung besteht wahrscheinlich aus dem Schreiben kleiner Klebeprogramme, und für solche kleinen Klebeprogramme können Sie jede Sprache verwenden, mit der Sie bereits vertraut sind und die gute Bibliotheken für das hat, was Sie tun müssen. Wenn Sie nur Daten von einer Windows-App in eine andere übertragen müssen, verwenden Sie ruhig Visual Basic.

Sie können auch kleine Klebeprogramme in Lisp schreiben (ich verwende es als Desktop-Taschenrechner), aber der größte Gewinn für Sprachen wie Lisp liegt am anderen Ende des Spektrums, wo Sie anspruchsvolle Programme schreiben müssen, um schwierige Probleme unter harter Konkurrenz zu lösen. Ein gutes Beispiel ist das Flugpreissuchprogramm, das ITA Software an Orbitz lizenziert. Diese Leute betraten einen Markt, der bereits von zwei großen, etablierten Wettbewerbern, Travelocity und Expedia, dominiert wurde, und haben sie offenbar technologisch gedemütigt.

Der Kern der ITA-Anwendung ist ein 200.000 Zeilen langes Common-Lisp-Programm, das viele Größenordnungen mehr Möglichkeiten durchsucht als ihre Wettbewerber, die offenbar immer noch Programmierungstechniken aus der Mainframe-Ära verwenden. (Obwohl ITA in gewisser Weise auch eine Programmiersprache aus der Mainframe-Ära verwendet.) Ich habe noch nie den Code von ITA gesehen, aber laut einem ihrer Top-Hacker verwenden sie viele Makros, und das überrascht mich nicht.

Zentripetale Kräfte

Ich sage nicht, dass der Einsatz unüblicher Technologien keine Kosten verursacht. Der Pointy-Haired Boss irrt sich nicht völlig, wenn er sich darüber Sorgen macht. Aber weil er die Risiken nicht versteht, neigt er dazu, sie zu übertreiben.

Ich kann mir drei Probleme vorstellen, die sich aus der Verwendung weniger gängiger Sprachen ergeben könnten. Ihre Programme funktionieren möglicherweise nicht gut mit Programmen, die in anderen Sprachen geschrieben sind. Ihnen stehen möglicherweise weniger Bibliotheken zur Verfügung. Und Sie könnten Schwierigkeiten haben, Programmierer einzustellen.

Wie groß ist jedes dieser Probleme? Die Bedeutung des ersten variiert je nachdem, ob Sie die Kontrolle über das gesamte System haben. Wenn Sie Software schreiben, die auf der Maschine eines Remotebenutzers laufen muss, auf einem fehlerhaften, geschlossenen Betriebssystem (ich nenne keine Namen), kann es Vorteile haben, Ihre Anwendung in der gleichen Sprache wie das Betriebssystem zu schreiben. Wenn Sie aber das gesamte System kontrollieren und den Quellcode aller Teile haben, wie es ITA vermutlich tut, können Sie jede beliebige Sprache verwenden. Wenn irgendwelche Inkompatibilitäten auftreten, können Sie sie selbst beheben.

Bei serverseitigen Anwendungen können Sie die fortschrittlichsten Technologien einsetzen, und das ist meiner Meinung nach die Hauptursache für das, was Jonathan Erickson als "Renaissance der Programmiersprachen" bezeichnet. Deshalb hören wir überhaupt von neuen Sprachen wie Perl und Python. Wir hören von diesen Sprachen nicht, weil Leute sie verwenden, um Windows-Apps zu schreiben, sondern weil Leute sie auf Servern verwenden. Und da sich die Software vom Desktop auf Server verlagert (eine Zukunft, in die sich selbst Microsoft zu ergeben scheint), wird der Druck, mittelmäßige Technologien zu verwenden, immer geringer.

Was die Bibliotheken angeht, so hängt ihre Bedeutung ebenfalls von der Anwendung ab. Bei weniger anspruchsvollen Problemen kann die Verfügbarkeit von Bibliotheken die innere Leistungsfähigkeit der Sprache überwiegen. Wo liegt der Schnittpunkt? Schwer genau zu sagen, aber wo auch immer er liegt, er liegt unterhalb von etwas, das man wahrscheinlich als Anwendung bezeichnen würde. Wenn ein Unternehmen der Meinung ist, dass es im Softwaregeschäft tätig ist und eine Anwendung schreibt, die eines seiner Produkte sein wird, dann wird es wahrscheinlich mehrere Hacker beschäftigen und mindestens sechs Monate dauern. In einem Projekt dieser Größe überwiegen leistungsfähige Sprachen wahrscheinlich die Bequemlichkeit vorgefertigter Bibliotheken.

Die dritte Sorge des spitzköpfigen Chefs, die Schwierigkeit, Programmierer einzustellen, halte ich für eine Nebelkerze. Wie viele Hacker müssen Sie denn einstellen? Sicher wissen wir inzwischen alle, dass Software am besten von Teams mit weniger als zehn Personen entwickelt wird. Und Sie sollten keine Schwierigkeiten haben, Hacker in diesem Umfang für jede Sprache einzustellen, von der Sie je gehört haben. Wenn Sie keine zehn Lisp-Hacker finden können, ist Ihr Unternehmen wahrscheinlich in der falschen Stadt für die Softwareentwicklung angesiedelt.

Tatsächlich verringert die Wahl einer leistungsfähigeren Sprache wahrscheinlich die Größe des Teams, das Sie benötigen, denn (a) wenn Sie eine leistungsfähigere Sprache verwenden, werden Sie wahrscheinlich nicht so viele Hacker brauchen, und (b) Hacker, die in fortgeschritteneren Sprachen arbeiten, sind wahrscheinlich intelligenter.

Ich sage nicht, dass Sie keinen Druck bekommen werden, vermeintlich "Standard"-Technologien zu verwenden. Bei Viaweb (jetzt Yahoo Store) haben wir bei Risikokapitalgebern und potenziellen Käufern einige Augenbrauen hochgezogen, indem wir Lisp verwendet haben. Aber wir haben auch Augenbrauen hochgezogen, indem wir generische Intel-Boxen als Server anstelle von "industriefesten" Servern wie Suns verwendet haben, indem wir eine damals noch unbekannte Open-Source-Unix-Variante namens FreeBSD anstelle eines echten kommerziellen Betriebssystems wie Windows NT verwendet haben und indem wir einen angeblichen E-Commerce-Standard namens SET ignoriert haben, an den sich heute niemand mehr erinnert.

Sie können nicht zulassen, dass die Anzugträger die technischen Entscheidungen für Sie treffen. Hat es einige potenzielle Käufer beunruhigt, dass wir Lisp verwendet haben? Etwas, aber wenn wir Lisp nicht verwendet hätten, wären wir nicht in der Lage gewesen, die Software zu schreiben, die sie dazu brachte, uns kaufen zu wollen. Was ihnen wie eine Anomalie erschien, war in Wirklichkeit Ursache und Wirkung.

Wenn Sie ein Start-up gründen, entwerfen Sie Ihr Produkt nicht, um Risikokapitalgeber oder potenzielle Käufer zu gefallen. Entwerfen Sie Ihr Produkt, um die Nutzer zu begeistern. Wenn Sie die Nutzer gewinnen, wird alles andere folgen. Und wenn Sie das nicht tun, wird sich niemand dafür interessieren, wie beruhigend orthodox Ihre Technologieauswahl war.

Die Kosten des Durchschnittlichen

Wie viel verlieren Sie, wenn Sie eine weniger leistungsfähige Sprache verwenden? Es gibt tatsächlich einige Daten darüber.

Das bequemste Maß für Leistungsfähigkeit ist wahrscheinlich die Codemenge. Der Zweck von Hochsprachen ist es, Ihnen größere Abstraktionen - größere Bausteine sozusagen - zu geben, damit Sie weniger davon benötigen, um eine Mauer einer bestimmten Größe zu bauen. Je leistungsfähiger also die Sprache, desto kürzer das Programm (nicht einfach in Zeichen, sondern in eindeutigen Elementen).

Wie ermöglicht Ihnen eine leistungsfähigere Sprache, kürzere Programme zu schreiben? Eine Technik, die Sie verwenden können, wenn die Sprache es zulässt, ist etwas, das man Bottom-up-Programmierung nennt. Anstatt Ihre Anwendung einfach in der Basissprache zu schreiben, bauen Sie auf der Basissprache eine Sprache auf, um Programme wie Ihres zu schreiben, und schreiben dann Ihr Programm darin. Der kombinierte Code kann viel kürzer sein als wenn Sie Ihr gesamtes Programm in der Basissprache geschrieben hätten - tatsächlich ist dies die Funktionsweise der meisten Kompressionsalgorithmen. Ein Bottom-up-Programm sollte auch leichter zu modifizieren sein, da in vielen Fällen die Sprachebene gar nicht geändert werden muss.

Die Codemenge ist wichtig, denn die Zeit, die es braucht, ein Programm zu schreiben, hängt hauptsächlich von seiner Länge ab. Wenn Ihr Programm in einer anderen Sprache dreimal so lang wäre, würde es auch dreimal so lange dauern, es zu schreiben - und Sie können das nicht umgehen, indem Sie mehr Leute einstellen, denn ab einer bestimmten Größe sind neue Mitarbeiter tatsächlich ein Nettoverlust. Fred Brooks hat dieses Phänomen in seinem berühmten Buch The Mythical Man-Month beschrieben, und alles, was ich gesehen habe, hat seine Aussagen bestätigt.

Wie viel kürzer sind also Ihre Programme, wenn Sie sie in Lisp schreiben? Die meisten Zahlen, die ich für Lisp im Vergleich zu C gehört habe, lagen bei etwa 7-10x. Aber in einem kürzlichen Artikel über ITA in der New Architect-Zeitschrift hieß es, dass "eine Zeile Lisp 20 Zeilen C ersetzen kann", und da dieser Artikel voller Zitate vom Präsidenten von ITA war, gehe ich davon aus, dass sie diese Zahl von ITA selbst bekommen haben. Wenn dem so ist, können wir ihr also Glauben schenken; Die Software von ITA enthält eine Menge C und C++ sowie Lisp, also sprechen sie aus Erfahrung.

Meine Vermutung ist, dass diese Vielfachen nicht einmal konstant sind. Ich denke, sie nehmen zu, wenn man sich schwierigeren Problemen gegenübersieht und auch, wenn man klügere Programmierer hat. Ein wirklich guter Hacker kann mehr aus besseren Werkzeugen herausholen.

Als einen Datenpunkt auf der Kurve, wenn Sie mit ITA konkurrieren und sich entscheiden würden, Ihre Software in C zu schreiben, könnten sie die Software zwanzigmal schneller entwickeln als Sie. Wenn Sie ein Jahr an einem neuen Feature arbeiten würden, könnten sie es in weniger als drei Wochen duplizieren. Während wenn sie gerade erst drei Monate an etwas Neuem arbeiten würden, es fünf Jahre dauern würde, bis Sie es auch hätten.

Und wissen Sie was? Das ist das Bestfall-Szenario. Wenn Sie über Codemenge-Verhältnisse sprechen, setzen Sie implizit voraus, dass Sie das Programm tatsächlich in der schwächeren Sprache schreiben können. Aber es gibt tatsächlich Grenzen dafür, was Programmierer leisten können. Wenn Sie versuchen, ein schwieriges Problem mit einer zu niedrigen Sprache zu lösen, erreichen Sie einen Punkt, an dem es einfach zu viel ist, was Sie im Kopf behalten müssen.

Wenn ich also sage, dass es fünf Jahre dauern würde, bis der hypothetische Wettbewerber von ITA etwas duplizieren könnte, das ITA in Lisp in drei Monaten schreiben kann, meine ich fünf Jahre, wenn nichts schief geht. Tatsächlich ist es in den meisten Unternehmen so, dass jedes Entwicklungsprojekt, das fünf Jahre dauern würde, wahrscheinlich nie fertig gestellt wird.

Ich gebe zu, dass dies ein Extremfall ist. Die Hacker von ITA scheinen außergewöhnlich intelligent zu sein, und C ist eine ziemlich niedrige Sprache. Aber auf einem wettbewerbsintensiven Markt wäre selbst ein Differenzial von zwei oder drei zu eins ausreichend, um sicherzustellen, dass Sie immer hinterherhinken.

Ein Rezept

Diese Art von Möglichkeit will der spitzköpfige Chef gar nicht erst in Betracht ziehen. Und so tun die meisten von ihnen es auch nicht. Denn letztendlich stört es den spitzköpfigen Chef nicht, wenn sein Unternehmen den Kürzeren zieht, solange niemand beweisen kann, dass es seine Schuld ist. Der sicherste Plan für ihn persönlich ist es, sich eng an die Mitte der Herde zu halten.

Innerhalb großer Organisationen wird der Ausdruck, der diese Herangehensweise beschreibt, als "Best Practice der Branche" bezeichnet. Ihr Zweck ist es, den spitzköpfigen Chef vor der Verantwortung zu schützen: Wenn er etwas wählt, das "Best Practice der Branche" ist und das Unternehmen verliert, kann er nicht dafür verantwortlich gemacht werden. Er hat nicht gewählt, die Branche hat es getan.

Ich glaube, dieser Begriff wurde ursprünglich verwendet, um Buchhaltungsmethoden und so weiter zu beschreiben. Was er grob bedeutet, ist nichts Ungewöhnliches tun. Und in der Buchhaltung ist das wahrscheinlich eine gute Idee. Die Begriffe "Cutting-Edge" und "Buchhaltung" klingen nicht gut zusammen. Aber wenn Sie dieses Kriterium in Entscheidungen über Technologie einführen, beginnen Sie, die falschen Antworten zu bekommen.

Technologie sollte oft Cutting-Edge sein. In Programmiersprachen, wie Erann Gat betont hat, führt das, was "Best Practice der Branche" tatsächlich ist, nicht zum Besten, sondern lediglich zum Durchschnitt. Wenn eine Entscheidung dazu führt, dass Sie Software mit einer Fraktion der Rate Ihrer aggressiveren Wettbewerber entwickeln, ist "Best Practice" ein Euphemismus.

Also haben wir hier zwei Informationen, die ich für sehr wertvoll halte. Tatsächlich weiß ich es aus eigener Erfahrung. Nummer 1, Sprachen unterscheiden sich in ihrer Leistungsfähigkeit. Nummer 2, die meisten Manager ignorieren dies bewusst. Zusammengenommen sind diese beiden Tatsachen buchstäblich ein Rezept, um Geld zu verdienen. ITA ist ein Beispiel für dieses Rezept in Aktion. Wenn Sie in einem Softwareunternehmen gewinnen wollen, nehmen Sie einfach das schwierigste Problem, das Sie finden können, verwenden Sie die leistungsfähigste Sprache, die Sie bekommen können, und warten Sie darauf, dass die spitzköpfigen Chefs Ihrer Wettbewerber zum Mittelmaß zurückkehren.

Anhang: Leistungsfähigkeit

Als Illustration dessen, was ich mit der relativen Leistungsfähigkeit von Programmiersprachen meine, betrachten Sie bitte das folgende Problem. Wir möchten eine Funktion schreiben, die Akkumulatoren erzeugt - eine Funktion, die eine Zahl n nimmt und eine Funktion zurückgibt, die eine andere Zahl i nimmt und n um i inkrementiert.

(Das ist inkrementiert um, nicht plus. Ein Akkumulator muss akkumulieren.)

In Common Lisp sähe das so aus:

(defun foo (n)
(lambda (i) (incf n i)))

und in Perl 5:

sub foo {
my ($n) = @_;
sub {$n += shift}
}

was mehr Elemente hat als die Lisp-Version, da Sie in Perl Parameter manuell extrahieren müssen.

In Smalltalk ist der Code etwas länger als in Lisp:

foo: n
|s|
s := n.
^[:i| s := s+i. ]

da, obwohl im Allgemeinen lexikalische Variablen funktionieren, Sie keine Zuweisung an einen Parameter vornehmen können, so dass Sie eine neue Variable s erstellen müssen.

In JavaScript ist das Beispiel wieder etwas länger, da JavaScript den Unterschied zwischen Anweisungen und Ausdrücken beibehält, so dass Sie explizite Rückgabeanweisungen benötigen, um Werte zurückzugeben:

function foo(n) {
return function (i) {
return n += i } }

(Um fair zu sein, Perl behält auch diesen Unterschied bei, geht aber in typischer Perl-Manier damit um, indem Sie Rückgaben weglassen können.)

Wenn Sie versuchen, den Lisp/Perl/Smalltalk/JavaScript-Code in Python zu übersetzen, stoßen Sie auf einige Einschränkungen. Da Python lexikalische Variablen nicht vollständig unterstützt, müssen Sie eine Datenstruktur erstellen, um den Wert von n zu halten. Und obwohl Python einen Funktionstyp hat, gibt es keine literale Darstellung dafür (es sei denn, der Körper ist nur ein einziger Ausdruck), so dass Sie eine benannte Funktion erstellen müssen, um sie zurückzugeben. Das ist das Ergebnis:

def foo(n):
s = [n]
def bar(i):
s[0] += i
return s[0]
return bar

Python-Nutzer könnten legitim fragen, warum sie nicht einfach schreiben können:

def foo(n):
return lambda i: return n += i

oder sogar

def foo(n):
lambda i: n += i

und meine Vermutung ist, dass sie das wahrscheinlich eines Tages tun werden. (Aber wenn sie nicht auf Python warten wollen, um den Rest des Weges in Lisp zu entwickeln, könnten sie immer noch...)

In objektorientieren Sprachen können Sie in gewissem Maße eine Closure (eine Funktion, die sich auf in umgebenden Bereichen definierte Variablen bezieht) simulieren, indem Sie eine Klasse mit einer Methode und einem Feld definieren, um jede Variable aus einem umgebenden Bereich zu ersetzen. Dies lässt den Programmierer die Art von Codeanalyse durchführen, die der Compiler in einer Sprache mit vollständiger Unterstützung für den lexikalischen Geltungsbereich durchführen würde, und es funktioniert nicht, wenn mehr als eine Funktion auf dieselbe Variable verweist, aber es reicht in einfachen Fällen wie diesem aus.

Python-Experten scheinen übereinzustimmen, dass dies der bevorzugte Weg ist, das Problem in Python zu lösen, entweder durch:

def foo(n):
class acc:
def __init__(self, s):
self.s = s
def inc(self, i):
self.s += i
return self.s
return acc(n).inc

oder

class foo:
def __init__(self, n):
self.n = n
def __call__(self, i):
self.n += i
return self.n

Ich führe diese an, weil ich nicht möchte, dass Python-Befürworter sagen, ich würde die Sprache falsch darstellen, aber beide erscheinen mir komplexer als die erste Version. Sie tun dasselbe, richten einen separaten Ort ein, um den Akkumulator zu halten; es ist nur ein Feld in einem Objekt anstelle des Kopfes einer Liste. Und die Verwendung dieser speziellen, reservierten Feldnamen, insbesondere call, scheint ein wenig ein Hack zu sein.

In der Rivalität zwischen Perl und Python scheint der Anspruch der Python-Hacker zu sein, dass Python eine elegantere Alternative zu Perl ist, aber was dieser Fall zeigt, ist, dass Leistungsfähigkeit die ultimative Eleganz ist: Das Perl-Programm ist einfacher (hat weniger Elemente), auch wenn die Syntax ein wenig hässlicher ist.

Wie sieht es mit anderen Sprachen aus? In den anderen in diesem Vortrag erwähnten Sprachen - Fortran, C, C++, Java und Visual Basic - ist es nicht klar, ob Sie dieses Problem überhaupt lösen können. Ken Anderson sagt, dass der folgende Code in Java etwa so nah wie möglich kommt:

public interface Inttoint {
public int call(int i);
}
public static Inttoint foo(final int n) {
return new Inttoint() {
int s = n;
public int call(int i) {
s = s + i;
return s;
}};
}

Dies verfehlt die Spezifikation, da es nur für ganze Zahlen funktioniert. Nach vielen E-Mail-Austausch mit Java-Hackern würde ich sagen, dass das Schreiben einer ordnungsgemäß polymorphen Version, die sich wie die vorherigen Beispiele verhält, zwischen verdammt unbequem und unmöglich liegt. Wenn jemand eine schreiben möchte, wäre ich sehr neugierig darauf, sie zu sehen, aber persönlich habe ich aufgegeben.

Es ist natürlich nicht wörtlich wahr, dass Sie dieses Problem nicht in anderen Sprachen lösen können. Die Tatsache, dass alle diese Sprachen Turing-äquivalent sind, bedeutet, dass Sie streng genommen jedes Programm in einer von ihnen schreiben können. Wie würden Sie es also machen? Im Grenzfall, indem Sie einen Lisp-Interpreter in der weniger leistungsfähigen Sprache schreiben.

Das klingt wie ein Witz, passiert aber so oft in großen Programmierprojekten in unterschiedlichem Maße, dass es einen Namen für das Phänomen gibt, Greenspuns Zehntes Gesetz:

Jedes hinreichend komplizierte C- oder Fortran-Programm enthält eine ad-hoc-spezifizierte, fehlerhafte und langsame Implementierung der Hälfte von Common Lisp.

Wenn Sie versuchen, ein schwieriges Problem zu lösen, ist die Frage nicht, ob Sie eine mächtige genug Sprache verwenden werden, sondern ob Sie (a) eine mächtige Sprache verwenden, (b) einen De-facto-Interpreter für eine schreiben oder (c) selbst zu einem menschlichen Compiler für eine werden. Wir sehen dies bereits im Python-Beispiel beginnen, wo wir in der Tat den Code simulieren, den ein Compiler erzeugen würde, um eine lexikalische Variable zu implementieren.

Diese Praxis ist nicht nur üblich, sondern auch institutionalisiert. In der OO-Welt hört man zum Beispiel viel über "Muster". Ich frage mich, ob diese Muster nicht manchmal ein Beweis für Fall (c), den menschlichen Compiler, sind. Wenn ich Muster in meinen Programmen sehe, betrachte ich das als Ärger. Die Form eines Programms sollte nur das Problem widerspiegeln, das es lösen muss. Jede andere Regelmäßigkeit im Code ist für mich ein Zeichen dafür, dass ich Abstraktionen verwende, die nicht mächtig genug sind - oft, dass ich von Hand die Erweiterungen eines Makros generiere, das ich schreiben muss.

Anmerkungen

Der IBM 704 CPU war etwa so groß wie ein Kühlschrank, aber viel schwerer. Die CPU wog 3150 Pfund, und die 4 KB RAM waren in einem separaten Kasten, der weitere 4000 Pfund wog. Der Sub-Zero 690, einer der größten Haushaltskühlschränke, wiegt 656 Pfund.

Steve Russell schrieb auch das erste (digitale) Computerspiel, Spacewar, im Jahr 1962.

Wenn Sie einen spitzköpfigen Chef dazu bringen wollen, Ihnen zu erlauben, Software in Lisp zu schreiben, könnten Sie ihm erzählen, es sei XML.

Hier ist der Akkumulator-Generator in anderen Lisp-Dialekten:

Scheme: (define (foo n)
(lambda (i) (set! n (+ n i)) n))
Goo:    (df foo (n) (op incf n _)))
Arc:    (def foo (n) [++ n _])

Erann Gats traurige Geschichte über "Best Practices in der Industrie" bei JPL hat mich inspiriert, diesen allgemein falsch angewandten Begriff anzugehen.

Peter Norvig fand, dass 16 der 23 Muster in Design Patterns in Lisp "unsichtbar oder einfacher" waren.

Vielen Dank an die vielen Menschen, die meine Fragen zu verschiedenen Sprachen beantwortet und/oder Entwürfe dieses Textes gelesen haben, darunter Ken Anderson, Trevor Blackwell, Erann Gat, Dan Giffin, Sarah Harlin, Jeremy Hylton, Robert Morris, Peter Norvig, Guy Steele und Anton van Straaten. Sie tragen keine Verantwortung für die geäußerten Meinungen.

Verwandte Themen:

Viele Menschen haben auf diesen Vortrag reagiert, daher habe ich eine zusätzliche Seite eingerichtet, um die von ihnen aufgeworfenen Fragen zu behandeln: Re: Revenge of the Nerds.

Es löste auch eine umfangreiche und oft nützliche Diskussion in der LL1-Mailingliste aus. Siehe insbesondere die Mail von Anton van Straaten über semantische Kompression.

Einige der Mails auf LL1 führten mich dazu, tiefer in das Thema der Sprachkraft in Succinctness is Power einzutauchen.

Eine größere Sammlung kanonischer Implementierungen des Akkumulator-Generator-Benchmarks ist auf einer eigenen Seite zusammengestellt.

Japanische Übersetzung, Spanische Übersetzung, Chinesische Übersetzung