Loading...

DIE RACHE DER NERDS

Original

Mai 2002

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

  • Guy Steele, Mitautor der Java-Spezifikation

In der Softwarebranche gibt es einen anhaltenden Kampf zwischen den spitzköpfigen Akademikern und einer anderen, ebenso beeindruckenden Kraft: den spitzkopfigen Chefs. Jeder weiß, wer der spitzkopfige Chef ist, oder? Ich denke, die meisten Leute in der Technologiebranche erkennen nicht nur diese Comicfigur, sondern kennen auch die Person in ihrem Unternehmen, nach der sie modelliert ist.

Der spitzkopfige Chef kombiniert auf wundersame Weise zwei Eigenschaften, die für sich genommen häufig vorkommen, aber selten zusammen gesehen werden: (a) er weiß absolut nichts über Technologie und (b) er hat sehr starke Meinungen darüber.

Nehmen wir zum Beispiel an, Sie müssen ein Softwarestück schreiben. Der spitzkopfige Chef hat keine Ahnung, wie diese Software funktionieren muss, und kann eine Programmiersprache von einer anderen nicht unterscheiden, und dennoch weiß er, in welcher Sprache Sie sie schreiben sollen. Genau. Er denkt, Sie sollten sie in Java schreiben.

Warum denkt er das? Werfen wir einen Blick ins Gehirn des spitzkopfigen Chefs. Was er denkt, ist so etwas. Java ist ein Standard. Ich weiß, dass es so sein muss, weil ich ständig in der Presse darüber lese. Da es ein Standard ist, werde ich keine Probleme bekommen, wenn ich ihn verwende. Und das bedeutet auch, dass es immer viele Java-Programmierer geben wird. Wenn die Programmierer, die jetzt für mich arbeiten, also kündigen, wie Programmierer, die für mich arbeiten, auf mysteriöse Weise immer tun, kann ich sie leicht ersetzen.

Nun, das klingt nicht so unvernünftig. Aber es basiert alles auf einer unausgesprochenen Annahme, und diese Annahme erweist sich als falsch. Der spitzkopfige Chef glaubt, dass alle Programmiersprachen so ziemlich gleichwertig sind. Wenn das wahr wäre, wäre er genau richtig. Wenn Sprachen alle gleichwertig sind, verwenden Sie einfach die Sprache, die jeder andere verwendet.

Aber alle Sprachen sind nicht gleichwertig, und ich glaube, ich kann Ihnen das beweisen, ohne überhaupt auf die Unterschiede zwischen ihnen einzugehen. Wenn Sie den spitzkopfigen Chef 1992 gefragt hätten, in welcher Sprache Software geschrieben werden sollte, hätte er mit so wenig Zögern geantwortet wie heute. Software sollte in C++ geschrieben werden. Aber wenn Sprachen alle gleichwertig sind, warum sollte sich die Meinung des spitzkopfigen Chefs jemals ändern? Warum sollten sich die Entwickler von Java überhaupt die Mühe gemacht haben, eine neue Sprache zu entwickeln?

Wenn Sie eine neue Sprache erstellen, liegt das vermutlich daran, dass Sie denken, dass sie in gewisser Weise besser ist als das, was die Leute bereits hatten. Und tatsächlich macht Gosling im ersten Java-Whitepaper deutlich, dass Java entwickelt wurde, um einige Probleme mit C++ zu beheben. Da haben Sie es: Sprachen sind nicht alle gleichwertig. Wenn Sie dem Pfad durch das Gehirn des spitzkopfigen Chefs zu Java folgen und dann durch die Geschichte von Java zu seinen Ursprüngen zurückgehen, halten Sie am Ende eine Idee in der Hand, die der Annahme widerspricht, mit der Sie angefangen haben.

Also, wer hat Recht? James Gosling oder der spitzkopfige Chef? Es überrascht nicht, dass Gosling Recht hat. Manche 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 beide?

Sobald Sie diese Frage zu berücksichtigen beginnen, haben Sie eine echte Büchse der Pandora geöffnet. Wenn der spitzkopfige Chef über das Problem in seiner ganzen Komplexität nachdenken müsste, würde ihm das Gehirn explodieren. Solange er alle Sprachen für gleichwertig hält, muss er nur diejenige auswählen, die den größten Schwung zu haben scheint. Da dies eher eine Frage der Mode als der Technologie ist, kann er wahrscheinlich sogar die richtige Antwort finden. Aber wenn Sprachen variieren, muss er plötzlich zwei Gleichungen gleichzeitig lösen und versuchen, ein optimales Gleichgewicht zwischen zwei Dingen zu finden, von denen er nichts versteht: die relative Eignung der zwanzig oder so führenden Sprachen für das Problem, das er lösen muss, und die Wahrscheinlichkeit, Programmierer, Bibliotheken usw. für jede Sprache zu finden. Wenn das auf der anderen Seite der Tür liegt, ist es keine Überraschung, dass der spitzkopfige Chef sie nicht öffnen möchte.

Der Nachteil, zu glauben, dass alle Programmiersprachen gleichwertig sind, ist, dass es nicht stimmt. 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 angenehme Idee.

Wir wissen, dass Java ziemlich gut sein muss, weil es die coole, neue Programmiersprache ist. Oder doch? Wenn Sie die Welt der Programmiersprachen aus der Ferne betrachten, sieht es so aus, als wäre Java das Neueste. (Ausreichend weit weg können Sie nur die große, blinkende Werbetafel sehen, die von Sun bezahlt wird.) Aber wenn Sie diese Welt aus der Nähe betrachten, stellen Sie fest, dass es verschiedene Grade von Coolness gibt. Innerhalb der Hacker-Subkultur gibt es eine andere Sprache namens Perl, die als viel cooler als Java angesehen wird. Slashdot zum Beispiel wird mit Perl generiert. Ich glaube nicht, dass diese Leute Java Server Pages verwenden würden. Aber es gibt eine andere, neuere Sprache namens Python, deren Benutzer dazu neigen, auf Perl herabzuschauen, und mehr warten in den Flügeln.

Wenn Sie sich diese Sprachen in der Reihenfolge Java, Perl, Python ansehen, bemerken Sie ein interessantes Muster. Zumindest bemerken Sie dieses Muster, wenn Sie ein Lisp-Hacker sind. Jede Sprache ähnelt zunehmend Lisp. Python kopiert sogar Funktionen, die viele Lisp-Hacker für Fehler halten. Sie könnten einfache Lisp-Programme Zeile für Zeile in Python übersetzen. Es ist 2002 und Programmiersprachen haben fast 1958 eingeholt.

Aufholen mit der Mathematik

Was ich meine ist, dass Lisp erstmals 1958 von John McCarthy entdeckt wurde und gängige Programmiersprachen erst jetzt die Ideen einholen, die er damals entwickelt hat.

Wie kann das überhaupt sein? Ist Computertechnologie nicht etwas, das sich sehr schnell verändert? Ich meine, 1958 waren Computer Kühlschrank-große Ungetüme mit der Rechenleistung einer Armbanduhr. Wie kann eine so alte Technologie überhaupt noch relevant sein, geschweige denn den neuesten Entwicklungen überlegen sein?

Ich sage Ihnen, wie. Das liegt daran, dass Lisp nicht wirklich als Programmiersprache konzipiert wurde, zumindest nicht im Sinne von heute. Was wir unter einer Programmiersprache verstehen, ist etwas, das wir verwenden, um einem Computer mitzuteilen, was er tun soll. McCarthy hatte irgendwann vor, eine Programmiersprache in diesem Sinne zu entwickeln, aber das Lisp, das wir letztendlich erhalten haben, basierte auf etwas Getrenntem, 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 eleganter war als Turing-Maschinen, war, 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.... Um eval zu schreiben, musste man eine Notation erfinden, die Lisp-Funktionen als Lisp-Daten darstellt, und eine solche Notation wurde für die Zwecke des Papiers entwickelt, ohne zu denken, dass sie verwendet werden würde, um Lisp-Programme in der Praxis auszudrücken.

Was als Nächstes geschah, war, dass Steve Russell, einer von McCarthys Doktoranden, diese Definition von eval Ende 1958 betrachtete und erkannte, dass, wenn er sie in Maschinensprache übersetzte, das Ergebnis ein Lisp-Interpreter wäre.

Dies war damals 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 Rechnen. Aber er machte weiter und tat es. Das heißt, er kompilierte das eval in meinem Papier in [IBM] 704-Maschinencode, behob Fehler und werbe dann mit diesem als Lisp-Interpreter, was es natürlich war. An diesem Punkt hatte Lisp also im Wesentlichen die Form, die es heute hat....

Plötzlich, innerhalb weniger Wochen, glaube ich, fand McCarthy seine theoretische Übung in eine tatsächliche Programmiersprache verwandelt – und eine, die leistungsfähiger war, als er beabsichtigt hatte.

Die kurze Erklärung, warum diese Sprache aus den 1950ern nicht veraltet ist, ist also, dass es nicht um Technologie, sondern um Mathematik ging, und Mathematik wird nicht alt. Der richtige Vergleichspunkt für Lisp ist nicht Hardware aus den 1950ern, sondern beispielsweise der Quicksort-Algorithmus, der 1960 entdeckt wurde und immer noch der schnellste Sortieralgorithmus für allgemeine Zwecke ist.

Es gibt noch eine andere Sprache, die aus den 1950ern noch existiert, Fortran, und sie repräsentiert den gegenteiligen Ansatz zum Sprachdesign. Lisp war ein Stück Theorie, das unerwartet zu einer Programmiersprache wurde. Fortran wurde absichtlich als Programmiersprache entwickelt, aber eine, die wir jetzt als sehr niedrigstufige betrachten würden.

Fortran I, die Sprache, die 1956 entwickelt wurde, war ein ganz anderes Tier als das heutige Fortran. Fortran I war so ziemlich Assemblersprache mit Mathematik. In gewisser Weise war es weniger leistungsfähig als neuere Assemblersprachen; es gab zum Beispiel keine Unterprogramme, nur Verzweigungen. Das heutige Fortran ist jetzt wohl näher an Lisp als an Fortran I.

Lisp und Fortran waren die Stämme von zwei verschiedenen Evolutionsbäumen, einer mit Wurzeln in der Mathematik und einer mit Wurzeln in der Maschinenarchitektur. Diese beiden Bäume konvergieren seitdem. Lisp begann leistungsstark und wurde in den nächsten zwanzig Jahren schnell. Sogenannte Mainstream-Sprachen begannen schnell und wurden in den nächsten vierzig Jahren allmählich leistungsfähiger, bis die fortschrittlichsten von ihnen jetzt Lisp ziemlich nahe kommen. Nah, aber ihnen fehlen noch ein paar Dinge....

Was Lisp anders machte

Als es erstmals entwickelt wurde, verkörperte Lisp neun neue Ideen. Einige davon nehmen wir jetzt als selbstverständlich hin, andere sind nur in fortschrittlicheren Sprachen zu sehen, und zwei sind immer noch einzigartig für Lisp. Die neun Ideen sind in der Reihenfolge ihrer Übernahme durch den Mainstream:

Bedingungen. Eine Bedingung ist eine if-then-else-Konstruktion. Wir nehmen diese jetzt als selbstverständlich hin, aber Fortran I hatte sie nicht. Es hatte nur einen bedingten goto, der eng an den zugrunde liegenden Maschinenbefehl angelehnt war.

Ein Funktionstyp. In Lisp sind Funktionen ein Datentyp, genau wie ganze Zahlen oder Zeichenfolgen. Sie haben eine literale Darstellung, können in Variablen gespeichert, als Argumente übergeben werden usw.

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

Dynamische Typisierung. In Lisp sind alle Variablen effektiv Zeiger. Werte sind es, die Typen haben, nicht Variablen, und das Zuweisen oder Binden von Variablen bedeutet, Zeiger zu kopieren, nicht das, worauf sie zeigen.

Müllsammlung.

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

Diese Unterscheidung war in Fortran I natürlich, weil Sie Anweisungen nicht verschachteln konnten. Und so, während Sie Ausdrücke für die Mathematik benötigten, gab es keinen Sinn, dass etwas anderes einen Wert zurückgibt, weil es nichts geben konnte, das darauf wartet.

Diese Einschränkung verschwand mit dem Aufkommen blockstrukturierter Sprachen, aber zu diesem Zeitpunkt war es zu spät. Die Unterscheidung zwischen Ausdrücken und Anweisungen war verfestigt. Sie verbreitete sich von Fortran nach Algol und dann zu ihren beiden Nachkommen.

Ein Symboltyp. Symbole sind effektiv Zeiger auf Zeichenfolgen, die in einer Hashtabelle gespeichert sind. So können Sie die Gleichheit testen, indem Sie einen Zeiger vergleichen, anstatt jedes Zeichen zu vergleichen.

Eine Notation für Code mit Bäumen aus Symbolen und Konstanten.

Die gesamte Sprache die ganze Zeit. Es gibt keinen wirklichen Unterschied zwischen Lesezeit, Kompilierzeit und Laufzeit. Sie können Code während des Lesens kompilieren oder ausführen, Code während des Kompilierens lesen oder ausführen und Code zur Laufzeit lesen oder kompilieren.

Code zur Lesezeit auszuführen, ermöglicht es Benutzern, die Syntax von Lisp neu zu programmieren; Code zur Kompilierzeit auszuführen, ist die Grundlage für Makros; Kompilieren zur Laufzeit ist die Grundlage für die Verwendung von Lisp als Erweiterungssprache in Programmen wie Emacs; und Lesen zur Laufzeit ermöglicht es Programmen, über S-Ausdrücke zu kommunizieren, eine Idee, die vor kurzem als XML neu erfunden wurde.

Als Lisp zum ersten Mal auftauchte, waren diese Ideen weit entfernt von der gängigen Programmierpraxis, die größtenteils von der Hardware diktiert wurde, die Ende der 1950er Jahre verfügbar war. Im Laufe der Zeit hat sich die Standardsprache, die in einer Reihe von populären Sprachen verkörpert ist, allmählich zu Lisp entwickelt. Ideen 1-5 sind jetzt weit verbreitet. Nummer 6 taucht im Mainstream auf. Python hat eine Form von 7, obwohl es dafür keine Syntax zu geben scheint.

Was Nummer 8 betrifft, könnte dies das Interessanteste von allen sein. Die Ideen 8 und 9 wurden nur zufällig Teil von Lisp, weil Steve Russell etwas implementierte, das McCarthy nie implementiert haben wollte. Und doch erweisen sich diese Ideen als verantwortlich für Lisps eigenartiges Aussehen und seine markantesten Merkmale. Lisp wirkt seltsam, nicht so sehr, weil es eine seltsame Syntax hat, sondern weil es keine Syntax hat; man drückt Programme direkt in den Parsebäumen aus, die hinter den Kulissen erstellt werden, wenn andere Sprachen geparst werden, und diese Bäume bestehen aus Listen, die Lisp-Datenstrukturen sind.

Die Sprache in ihren eigenen Datenstrukturen auszudrücken, erweist sich als ein sehr mächtiges Merkmal. Die Ideen 8 und 9 zusammen bedeuten, dass Sie Programme schreiben können, die Programme schreiben. Das mag wie eine bizarre Idee klingen, aber in Lisp ist es eine alltägliche Sache. Der gebräuchlichste Weg, dies zu tun, ist mit etwas, das man ein Makro nennt.

Der Begriff "Makro" bedeutet in Lisp nicht das, was er in anderen Sprachen bedeutet. Ein Lisp-Makro kann alles sein, von einer Abkürzung bis hin zu einem Compiler für eine neue Sprache. Wenn Sie Lisp wirklich verstehen wollen, oder einfach nur Ihren Programmierhorizont erweitern wollen, würde ich mehr erfahren über Makros.

Makros (im Lisp-Sinne) sind, soweit ich weiß, immer noch einzigartig für Lisp. Dies liegt zum Teil daran, dass man, um Makros zu haben, seine Sprache wahrscheinlich so seltsam aussehen lassen muss wie Lisp. Es könnte auch daran liegen, dass man, wenn man diese letzte Steigerung der Leistung 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 Funktionen hat, die als Listen ausgedrückt werden, dann können Sie den Rest von Lisp daraus aufbauen. Das ist in der Tat die bestimmende Eigenschaft von Lisp: Es war, um dies zu ermöglichen, dass McCarthy Lisp die Form gab, die es hat.

Wo Sprachen wichtig sind

Nehmen wir also an, dass Lisp eine Art Grenze darstellt, an die sich Mainstream-Sprachen asymptotisch annähern - bedeutet das, dass man es tatsächlich zum Schreiben von Software verwenden sollte? Wie viel verliert man, wenn man eine weniger leistungsstarke Sprache verwendet? Ist es nicht manchmal klüger, nicht an der Spitze der Innovation zu sein? Und ist Popularität nicht bis zu einem gewissen Grad eine eigene Rechtfertigung? Hat der spitzbübige Chef zum Beispiel nicht Recht, wenn er eine Sprache verwenden möchte, für die er leicht Programmierer einstellen kann?

Es gibt natürlich Projekte, bei denen die Wahl der Programmiersprache nicht so wichtig ist. In der Regel gilt: Je anspruchsvoller die Anwendung ist, desto mehr Hebelwirkung gewinnt man durch den Einsatz einer leistungsstarken Sprache. Aber viele Projekte sind überhaupt nicht anspruchsvoll. Die meisten Programmierarbeiten bestehen wahrscheinlich aus dem Schreiben von kleinen Klebeprogrammen, und für kleine Klebeprogramme kann man jede Sprache verwenden, mit der man bereits vertraut ist und die über gute Bibliotheken für das verfügt, was man tun muss. Wenn man nur Daten aus einer Windows-Anwendung in eine andere übertragen muss, verwendet man natürlich Visual Basic.

Man kann auch kleine Klebeprogramme in Lisp schreiben (ich benutze es als Taschenrechner), aber der größte Gewinn für Sprachen wie Lisp liegt am anderen Ende des Spektrums, wo man ausgeklügelte Programme schreiben muss, um schwierige Probleme angesichts von hartem Wettbewerb zu lösen. Ein gutes Beispiel dafür ist das Flugpreis-Suchprogramm, das ITA Software an Orbitz lizenziert. Diese Jungs betraten einen Markt, der bereits von zwei großen, etablierten Wettbewerbern, Travelocity und Expedia, dominiert wurde, und scheinen sie technologisch einfach nur gedemütigt zu haben.

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

Zentripetalkräfte

Ich sage nicht, dass es keine Kosten für den Einsatz unüblicher Technologien gibt. Der spitzbübige Chef irrt sich nicht völlig, wenn er sich darüber Sorgen macht. Aber weil er die Risiken nicht versteht, neigt er dazu, sie zu vergrößern.

Ich kann mir drei Probleme vorstellen, die durch den Einsatz weniger gebräuchlicher Sprachen entstehen könnten. Ihre Programme funktionieren möglicherweise nicht gut mit Programmen, die in anderen Sprachen geschrieben wurden. Sie haben möglicherweise weniger Bibliotheken zur Verfügung. Und es könnte Ihnen schwerfallen, Programmierer einzustellen.

Wie groß ist das Problem bei jedem dieser Punkte? Die Bedeutung des ersten hängt davon ab, ob Sie die Kontrolle über das gesamte System haben. Wenn Sie Software schreiben, die auf dem Rechner eines Remote-Benutzers auf einem fehlerhaften, geschlossenen Betriebssystem ausgeführt werden muss (ich nenne keine Namen), gibt es möglicherweise Vorteile, wenn Sie Ihre Anwendung in der gleichen Sprache wie das Betriebssystem schreiben. Aber wenn Sie das gesamte System kontrollieren und den Quellcode aller Teile haben, wie es ITA wahrscheinlich tut, können Sie beliebige Sprachen verwenden. Wenn es zu einer Inkompatibilität kommt, können Sie diese selbst beheben.

Bei serverseitigen Anwendungen können Sie mit den fortschrittlichsten Technologien davonkommen, und ich denke, dass dies die Hauptursache für das ist, was Jonathan Erickson den "Renaissance der Programmiersprachen." nennt. Deshalb hören wir überhaupt von neuen Sprachen wie Perl und Python. Wir hören nicht von diesen Sprachen, weil die Leute sie zum Schreiben von Windows-Apps verwenden, sondern weil die Leute sie auf Servern verwenden. Und wenn sich Software weg vom Desktop und auf Server verlagert (eine Zukunft, die selbst Microsoft zu akzeptieren scheint), wird es immer weniger Druck geben, Mittelmaß-Technologien zu verwenden.

Was Bibliotheken betrifft, so hängt ihre Bedeutung auch von der Anwendung ab. Bei weniger anspruchsvollen Problemen kann die Verfügbarkeit von Bibliotheken die intrinsische Leistung der Sprache überwiegen. Wo liegt der Break-Even-Point? Schwer zu sagen, aber wo immer er liegt, er liegt kurz vor allem, was man als Anwendung bezeichnen würde. Wenn sich ein Unternehmen als Softwareunternehmen betrachtet und eine Anwendung schreibt, die eines seiner Produkte sein wird, wird sie wahrscheinlich mehrere Hacker umfassen und mindestens sechs Monate dauern. In einem Projekt dieser Größe überwiegen leistungsstarke Sprachen wahrscheinlich die Bequemlichkeit bereits existierender Bibliotheken.

Die dritte Sorge des spitzbübigen Chefs, die Schwierigkeit der Einstellung von Programmierern, halte ich für ein rotes Hering. Wie viele Hacker müssen Sie schließlich einstellen? Sicherlich wissen wir jetzt alle, dass Software am besten von Teams mit weniger als zehn Leuten entwickelt wird. Und Sie sollten keine Probleme haben, Hacker in diesem Umfang für jede Sprache einzustellen, von der jemand schon einmal gehört hat. Wenn Sie keine zehn Lisp-Hacker finden, dann ist Ihr Unternehmen wahrscheinlich in der falschen Stadt für die Softwareentwicklung angesiedelt.

Tatsächlich führt die Wahl einer leistungsstärkeren Sprache wahrscheinlich zu einer Reduzierung der benötigten Teamgröße, da (a) Sie bei der Verwendung einer leistungsstärkeren Sprache wahrscheinlich nicht so viele Hacker benötigen, und (b) Hacker, die in fortschrittlicheren Sprachen arbeiten, wahrscheinlich intelligenter sind.

Ich sage nicht, dass Sie nicht viel Druck bekommen werden, das zu verwenden, was als "Standard"-Technologien wahrgenommen wird. Bei Viaweb (heute Yahoo Store) haben wir bei VCs und potenziellen Übernehmern einige Augenbrauen hochgezogen, indem wir Lisp verwendet haben. Aber wir haben auch Augenbrauen hochgezogen, indem wir generische Intel-Boxen als Server verwendet haben anstelle von "industriellen Servern" wie Suns, indem wir eine damals noch obskure Open-Source-Unix-Variante namens FreeBSD verwendet haben anstelle eines echten kommerziellen Betriebssystems wie Windows NT, indem wir einen angeblichen E-Commerce-Standard namens SET ignoriert haben, an den sich heute niemand mehr erinnert, und so weiter.

Sie können die Anzüge nicht technische Entscheidungen für Sie treffen lassen. Hat es einige potenzielle Käufer beunruhigt, dass wir Lisp verwendet haben? Einige, leicht, aber wenn wir Lisp nicht verwendet hätten, hätten wir nicht in der Lage gewesen, die Software zu schreiben, die sie zum Kauf von uns bewogen hat. Was ihnen wie eine Anomalie erschien, war in Wirklichkeit Ursache und Wirkung.

Wenn Sie ein Startup gründen, entwerfen Sie Ihr Produkt nicht, um VCs oder potenzielle Käufer zu beeindrucken. Entwerfen Sie Ihr Produkt, um die Benutzer zu beeindrucken. Wenn Sie die Benutzer gewinnen, wird alles andere folgen. Und wenn Sie es nicht tun, wird sich niemand darum kümmern, wie tröstlich orthodox Ihre Technologieentscheidungen waren.

Die Kosten der Durchschnittlichkeit

Wie viel verliert man, wenn man eine weniger leistungsstarke Sprache verwendet? Es gibt tatsächlich einige Daten darüber.

Das bequemste Maß für Leistung ist wahrscheinlich die Codegröße. Der Sinn von Hochsprachen ist es, Ihnen größere Abstraktionen zu geben - größere Bausteine, sozusagen, damit Sie nicht so viele brauchen, um eine Wand einer bestimmten Größe zu bauen. Je leistungsstärker die Sprache ist, desto kürzer ist das Programm (nicht einfach in Zeichen, natürlich, sondern in unterschiedlichen Elementen).

Wie ermöglicht es Ihnen eine leistungsstärkere Sprache, kürzere Programme zu schreiben? Eine Technik, die Sie anwenden können, wenn die Sprache dies erlaubt, ist etwas, das man Bottom-up-Programmierung nennt. Anstatt einfach Ihre Anwendung in der Basissprache zu schreiben, bauen Sie auf der Basissprache eine Sprache zum Schreiben von Programmen wie Ihrer auf und schreiben dann Ihr Programm in dieser Sprache. Der kombinierte Code kann viel kürzer sein, als wenn Sie Ihr ganzes Programm in der Basissprache geschrieben hätten - tatsächlich funktionieren die meisten Kompressionsalgorithmen so. Ein Bottom-up-Programm sollte auch einfacher zu ändern sein, da sich die Sprachschicht in vielen Fällen gar nicht ändern muss.

Codegröße ist wichtig, weil die Zeit, die man zum Schreiben eines Programms benötigt, hauptsächlich von dessen Länge abhängt. Wenn Ihr Programm in einer anderen Sprache dreimal so lang wäre, würde es dreimal so lange dauern, es zu schreiben - und das können Sie nicht durch die Einstellung von mehr Leuten umgehen, weil über eine bestimmte Größe hinaus neue Mitarbeiter eigentlich eine Belastung sind. Fred Brooks beschrieb dieses Phänomen in seinem berühmten Buch The Mythical Man-Month, und alles, was ich gesehen habe, hat dazu neigen, zu bestätigen, was er sagte.

Wie viel kürzer sind Ihre Programme, wenn Sie sie in Lisp schreiben? Die meisten Zahlen, die ich für Lisp gegenüber C gehört habe, liegen beispielsweise bei etwa 7-10x. Aber ein aktueller Artikel über ITA in New Architect Magazin sagte, dass "eine Zeile Lisp 20 Zeilen C ersetzen kann", und da dieser Artikel voll von Zitaten von ITAs Präsident war, nehme ich an, dass sie diese Zahl von ITA haben. Wenn dem so ist, dann können wir uns darauf verlassen; ITAs Software enthält neben Lisp auch viel C und C++, so dass sie aus eigener Erfahrung sprechen.

Ich vermute, dass diese Vielfachen nicht einmal konstant sind. Ich denke, sie steigen, wenn Sie mit schwierigeren Problemen konfrontiert werden und auch, wenn Sie intelligentere Programmierer haben. Ein wirklich guter Hacker kann mehr aus besseren Tools herausholen.

Als Datenpunkt auf der Kurve, jedenfalls, wenn Sie mit ITA konkurrieren würden und sich dafür entscheiden würden, Ihre Software in C zu schreiben, könnten sie Software zwanzigmal schneller entwickeln als Sie. Wenn Sie ein Jahr an einer neuen Funktion arbeiten würden, könnten sie diese in weniger als drei Wochen duplizieren. Wenn sie hingegen nur drei Monate für die Entwicklung von etwas Neuem aufwenden würden, würden Sie fünf Jahre brauchen, bis Sie es auch hätten.

Und wissen Sie was? Das ist der Best-Case-Szenario. Wenn man über Code-Größenverhältnisse spricht, setzt man implizit voraus, dass man das Programm überhaupt in der schwächeren Sprache schreiben kann. Tatsächlich gibt es Grenzen für das, was Programmierer tun können. Wenn Sie versuchen, ein schwieriges Problem mit einer Sprache zu lösen, die zu niedrig ist, gelangen Sie an einen Punkt, an dem es einfach zu viel ist, was man gleichzeitig im Kopf behalten muss.

Wenn ich also sage, dass ITAs imaginärer Konkurrent fünf Jahre brauchen würde, um etwas zu duplizieren, das ITA in Lisp in drei Monaten schreiben könnte, meine ich fünf Jahre wenn nichts schiefgeht. Tatsächlich funktionieren die Dinge in den meisten Unternehmen so, dass jedes Entwicklungsprojekt, das fünf Jahre dauern würde, wahrscheinlich nie fertig gestellt wird.

Ich gebe zu, das ist ein Extremfall. ITAs Hacker scheinen ungewöhnlich intelligent zu sein, und C ist eine ziemlich niedrig-level Sprache. Aber in einem wettbewerbsintensiven Markt würde auch ein Unterschied von zwei oder drei zu eins genügen, um zu garantieren, dass Sie immer hinterherhinken würden.

Ein Rezept

Dies ist die Art von Möglichkeit, an die der spitzbübige Chef gar nicht denken möchte. Und so tun es die meisten auch nicht. Denn, wissen Sie, wenn es darauf ankommt, macht es dem spitzbübigen Chef nichts aus, wenn sein Unternehmen in den Hintern getreten wird, solange es niemand beweisen kann, dass es seine Schuld ist. Der sicherste Plan für ihn persönlich ist es, nahe an der Mitte der Herde zu bleiben.

In großen Organisationen wird der Ausdruck, der verwendet wird, um diesen Ansatz zu beschreiben, "Branchenbest-Practice" genannt. Sein Zweck ist es, den spitzbübigen Chef vor Verantwortung zu schützen: Wenn er sich für etwas entscheidet, das "Branchenbest-Practice" ist, und das Unternehmen verliert, kann er nicht verantwortlich gemacht werden. Er hat nicht gewählt, die Branche hat es getan.

Ich glaube, dieser Begriff wurde ursprünglich verwendet, um Buchhaltungsmethoden usw. zu beschreiben. Was er grob bedeutet, ist mach nichts Verrücktes. Und in der Buchhaltung ist das wahrscheinlich eine gute Idee. Die Begriffe "bahnbrechend" und "Buchhaltung" klingen nicht gut zusammen. Aber wenn man dieses Kriterium auf Entscheidungen über Technologie überträgt, fängt man an, die falschen Antworten zu bekommen.

Technologie sollte oft bahnbrechend sein. In Programmiersprachen, wie Erann Gat herausgefunden hat, ist "Industriestandard" eigentlich nicht das Beste, sondern lediglich der Durchschnitt. Wenn eine Entscheidung dazu führt, dass man Software mit einem Bruchteil der Geschwindigkeit von aggressiveren Wettbewerbern entwickelt, ist "Best Practice" ein falscher Begriff.

Also haben wir hier zwei Informationen, die ich für sehr wertvoll halte. Tatsächlich weiß ich es aus eigener Erfahrung. Nummer 1, Sprachen variieren in ihrer Leistung. Nummer 2, die meisten Manager ignorieren das bewusst. Zusammen sind diese beiden Tatsachen buchstäblich ein Rezept, um Geld zu verdienen. ITA ist ein Beispiel für dieses Rezept in Aktion. Wenn man in einem Software- Geschäft gewinnen will, muss man sich nur das schwierigste Problem suchen, die leistungsstärkste Sprache verwenden, die man bekommen kann, und warten, bis die spitzköpfigen Chefs der Konkurrenz zum Mittelwert zurückkehren.

Anhang: Leistung

Als Illustration, was ich mit der relativen Leistung von Programmiersprachen meine, betrachten Sie das folgende Problem. Wir wollen eine Funktion schreiben, die Akkumulatoren generiert - eine Funktion, die eine Zahl n nimmt und eine Funktion zurückgibt, die eine weitere Zahl i nimmt und n um i inkrementiert zurückgibt.

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

In Common Lisp wäre das


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

und in Perl 5,


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

welches mehr Elemente hat als die Lisp-Version, weil man Parameter in Perl manuell extrahieren muss.

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


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

denn obwohl im Allgemeinen lexikalische Variablen funktionieren, kann man keine Zuweisung an einen Parameter durchführen, so dass man eine neue Variable s erstellen muss.

In Javascript ist das Beispiel, wiederum, etwas länger, weil Javascript die Unterscheidung zwischen Anweisungen und Ausdrücken beibehält, so dass man explizite Rückgabe-Anweisungen benötigt, um Werte zurückzugeben:


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

(Um fair zu sein, behält Perl auch diese Unterscheidung bei, befasst sich aber auf typische Perl-Art damit, indem man die Rückgabe weglassen kann.)

Versucht man den Lisp/Perl/Smalltalk/Javascript-Code in Python zu übersetzen, stößt man auf einige Einschränkungen. Da Python lexikalische Variablen nicht vollständig unterstützt, muss man eine Datenstruktur erstellen, um den Wert von n zu speichern. Und obwohl Python einen Funktionsdatentyp hat, gibt es keine Literal-Darstellung dafür (es sei denn, der Rumpf ist nur ein einziger Ausdruck), so dass man eine benannte Funktion erstellen muss, um sie zurückzugeben. Das ist, was man am Ende erhält:


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

Python-Benutzer könnten zu Recht 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 darauf warten wollen, dass Python sich den Rest des Weges in Lisp entwickelt, könnten sie immer einfach...)

In OO-Sprachen kann man in begrenztem Umfang eine Closure (eine Funktion, die sich auf Variablen bezieht, die in umschließenden Gültigkeitsbereichen definiert sind) simulieren, indem man eine Klasse mit einer Methode und einem Feld definiert, um jede Variable aus einem umschließenden Gültigkeitsbereich zu ersetzen. Das führt den Programmierer dazu, die Art von Code zu analysieren, die von dem Compiler in einer Sprache mit vollständiger Unterstützung für lexikalische Gültigkeitsbereiche durchgeführt würde, und es wird nicht funktionieren, wenn sich mehr als eine Funktion auf die gleiche Variable bezieht, aber es reicht in einfachen Fällen wie diesem.

Python-Experten scheinen sich einig zu sein, dass dies die bevorzugte Art ist, das Problem in Python zu lösen, und schreiben entweder


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üge diese ein, weil ich nicht möchte, dass Python Befürworter sagen, dass ich die Sprache falsch dargestellt habe, aber beide scheinen mir komplexer zu sein als die erste Version. Man tut dasselbe, indem man einen separaten Ort einrichtet, um den Akkumulator zu speichern; es ist nur ein Feld in einem Objekt anstelle des Kopfes einer Liste. Und die Verwendung dieser speziellen, reservierten Feldnamen, insbesondere call, scheint ein bisschen ein Hack zu sein.

Im Wettbewerb zwischen Perl und Python scheint die Behauptung der Python-Hacker zu sein, dass Python eine elegantere Alternative zu Perl ist, aber was dieser Fall zeigt, ist, dass Leistung die ultimative Eleganz ist: das Perl-Programm ist einfacher (hat weniger Elemente), auch wenn die Syntax etwas hässlicher ist.

Wie sieht es mit anderen Sprachen aus? In den anderen Sprachen, die in diesem Vortrag erwähnt werden - Fortran, C, C++, Java und Visual Basic - ist es nicht klar, ob man dieses Problem tatsächlich lösen kann. Ken Anderson sagt, dass der folgende Code so nah wie möglich daran ist, was man in Java erreichen kann:


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 erfüllt die Spezifikation nicht, weil es nur für Ganzzahlen funktioniert. Nach vielen E-Mail-Austauschen mit Java-Hackern, würde ich sagen, dass das Schreiben einer richtig polymorphen Version, die sich wie die vorherigen Beispiele verhält, irgendwo zwischen verdammt umständlich und unmöglich liegt. Wenn jemand eine schreiben möchte, wäre ich sehr neugierig, sie zu sehen, aber ich persönlich habe die Zeit überschritten.

Es ist natürlich nicht buchstäblich wahr, dass man dieses Problem in anderen Sprachen nicht lösen kann. Die Tatsache, dass alle diese Sprachen Turing-äquivalent sind, bedeutet, dass man streng genommen jedes Programm in jeder von ihnen schreiben kann. Wie würde man das machen? Im Grenzfall, indem man einen Lisp Interpreter in der weniger leistungsstarken Sprache schreibt.

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

Jedes ausreichend komplexe C- oder Fortran-Programm enthält eine Ad-hoc- informell spezifizierte fehlerhafte, langsame Implementierung von der Hälfte von Common Lisp.

Wenn man versucht, ein schwieriges Problem zu lösen, stellt sich nicht die Frage, ob man eine genügend leistungsstarke Sprache verwenden wird, sondern ob man (a) eine leistungsstarke Sprache verwendet, (b) einen De-facto-Interpreter für eine schreibt, oder (c) selbst zu einem menschlichen Compiler für eine wird. Wir sehen das bereits anfangen in dem Python-Beispiel, wo wir in Wirklichkeit den Code simulieren, den ein Compiler generieren würde, um eine lexikalische Variable zu implementieren.

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

Hinweise

Die IBM 704 CPU war etwa so groß wie ein Kühlschrank, aber viel schwerer. Die CPU wog 3150 Pfund, und die 4K RAM waren in einer separaten Box, die 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 man einen spitzköpfigen Chef dazu bringen will, dass er einen Software in Lisp schreiben lässt, könnte man versuchen, ihm zu sagen, dass es XML ist.

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 "Industriestandard" bei JPL inspirierte mich, diese allgemein falsch angewandte Phrase anzusprechen.

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

Danke an die vielen Leute, die meine Fragen zu verschiedenen Sprachen beantwortet haben und/oder Entwürfe 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 Schuld für die geäußerten Meinungen.

Verwandte:

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

Es löste auch eine umfangreiche und oft nützliche Diskussion auf der LL1 Mailing-Liste aus. Besonders erwähnenswert ist die Mail von Anton van Straaten über semantische Kompression.

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

Eine größere Menge von kanonischen Implementierungen des Akkumulator- Generator-Benchmarks sind auf einer eigenen Seite zusammengestellt.

Japanische Übersetzung, Spanische Übersetzung, Chinesische Übersetzung