Rache der Nerds

Möchten Sie ein Startup gründen? Lassen Sie sich von Y Combinator finanzieren.


Mai 2002

| "Wir waren hinter den C++-Programmierern her. Wir haben es geschafft, viele von ihnen etwa auf halbem Weg zu Lisp zu ziehen."

- Guy Steele, Co-Autor der Java-Spezifikation

Im Softwaregeschäft gibt es einen ständigen Kampf zwischen den "spitzen Köpfen" der Akademiker und einer ebenso formidablen Kraft, den "spitzen Haar-Chefs". Jeder weiß, wer der spitze Haar-Chef ist, oder? Ich glaube, die meisten Leute in der Technologiewelt erkennen nicht nur diese Cartoon-Figur, sondern kennen auch die reale Person in ihrem Unternehmen, auf der sie basiert.

Der spitze Haar-Chef vereint auf wundersame Weise zwei Eigenschaften, die einzeln zwar häufig vorkommen, aber selten zusammen gesehen werden: (a) Er weiß absolut nichts über Technologie und (b) er hat sehr starke Meinungen dazu.

Nehmen wir an, Sie müssen zum Beispiel eine Software schreiben. Der spitze Haar-Chef hat keine Ahnung, wie diese Software funktionieren muss, und kann keine Programmiersprache von einer anderen unterscheiden, und doch weiß er, in welcher Sprache Sie sie schreiben sollten. Genau. Er denkt, Sie sollten sie in Java schreiben.

Warum denkt er das? Werfen wir einen Blick in das Gehirn des spitzen Haar-Chefs. Was er denkt, ist ungefähr so: Java ist ein Standard. Das muss es sein, denn ich lese ständig davon in der Presse. Da es ein Standard ist, werde ich keinen Ärger bekommen, wenn ich es benutze. Und das bedeutet auch, dass es immer viele Java-Programmierer geben wird, also wenn die Programmierer, die jetzt für mich arbeiten, kündigen, wie Programmierer, die für mich arbeiten, mysteriöserweise immer tun, kann ich sie leicht ersetzen.

Nun, das klingt nicht ganz unvernünftig. Aber es basiert alles auf einer unausgesprochenen Annahme, und diese Annahme stellt sich als falsch heraus. Der spitze Haar-Chef glaubt, dass alle Programmiersprachen ziemlich gleichwertig sind. Wenn das wahr wäre, wäre er auf dem richtigen Weg. Wenn Sprachen alle gleichwertig sind, sicher, verwenden Sie, was auch immer die Leute sonst noch benutzen.

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 spitzen Haar-Chef 1992 gefragt hätten, in welcher Sprache Software geschrieben werden sollte, hätte er genauso zögerlich geantwortet wie heute. Software sollte in C++ geschrieben werden. Aber wenn Sprachen alle gleichwertig sind, warum sollte sich die Meinung des spitzen Haar-Chefs jemals ändern? Tatsächlich, warum hätten die Entwickler von Java sich überhaupt die Mühe machen sollen, eine neue Sprache zu entwickeln?

Vermutlich, wenn Sie eine neue Sprache entwickeln, dann, weil Sie denken, dass sie in irgendeiner 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 die Spur durch das Gehirn des spitzen Haar-Chefs zu Java und dann durch die Geschichte von Java zurück zu ihren Ursprüngen verfolgen, landen Sie bei einer Idee, die die Annahme widerspricht, mit der Sie begonnen haben.

Also, wer hat Recht? James Gosling oder der spitze Haar-Chef? Wenig überraschend hat Gosling Recht. 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 ist C++? Gibt es Situationen, in denen andere Sprachen besser sind als beide?

Sobald Sie anfangen, diese Frage zu bedenken, haben Sie ein echtes Wespennest geöffnet. Wenn der spitze Haar-Chef über das Problem in seiner vollen Komplexität nachdenken müsste, würde sein Gehirn explodieren. Solange er alle Sprachen für gleichwertig hält, muss er nur diejenige auswählen, die die meiste Dynamik zu haben scheint, und 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 simultane Gleichungen lösen und versuchen, eine optimale Balance zwischen zwei Dingen zu finden, von denen er nichts weiß: die relative Eignung der etwa zwanzig führenden Sprachen für das Problem, das er lösen muss, und die Wahrscheinlichkeit, Programmierer, Bibliotheken usw. für jede zu finden. Wenn das auf der anderen Seite der Tür ist, ist es kein Wunder, dass der spitze Haar-Chef sie nicht öffnen möchte.

Der Nachteil des Glaubens, dass alle Programmiersprachen gleichwertig sind, ist, dass es nicht stimmt. Aber der Vorteil ist, dass es Ihr Leben erheblich vereinfacht. Und ich denke, das ist der Hauptgrund, warum die Idee so weit verbreitet ist. Es ist eine bequeme Idee.

Wir wissen, dass Java ziemlich gut sein muss, weil es die angesagte, neue Programmiersprache ist. Oder ist es das? Wenn man die Welt der Programmiersprachen aus der Ferne betrachtet, sieht es so aus, als sei Java das Neueste. (Ausreichend großer Entfernung sieht man nur das große, blinkende Werbeplakat, das von Sun bezahlt wird.) Aber wenn man diese Welt aus der Nähe betrachtet, stellt man fest, dass es Grade der 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 diese Leute Java Server Pages verwenden würden. Aber es gibt eine weitere, neuere Sprache namens Python, deren Benutzer dazu neigen, auf Perl herabzublicken, und mehr wartet im Hintergrund.

Wenn man sich diese Sprachen in dieser Reihenfolge ansieht: Java, Perl, Python, bemerkt man ein interessantes Muster. Zumindest bemerkt man dieses Muster, wenn man ein Lisp-Hacker ist. Jede ist progressiv Lisp-ähnlicher. Python kopiert sogar Funktionen, die viele Lisp-Hacker für Fehler halten. Man könnte einfache Lisp-Programme Zeile für Zeile in Python übersetzen. Es ist 2002, und Programmiersprachen haben die Ideen, die er 1958 entwickelt hat, fast eingeholt.

Aufholen mit 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.

Nun, wie kann das sein? Ist Computertechnologie nicht etwas, das sich sehr schnell verändert? Ich meine, 1958 waren Computer kühlschrankgroße Ungetüme mit der Rechenleistung eines Armbanduhr. Wie kann eine so alte Technologie überhaupt relevant sein, geschweige denn den neuesten Entwicklungen überlegen?

Ich sage Ihnen, wie. Das liegt daran, dass Lisp nicht wirklich als Programmiersprache konzipiert wurde, zumindest nicht in dem Sinne, wie wir es heute meinen. Was wir unter einer Programmiersprache verstehen, ist etwas, mit dem wir einem Computer sagen, was er tun soll. McCarthy beabsichtigte schließlich, eine Programmiersprache in diesem Sinne zu entwickeln, aber das Lisp, das wir tatsächlich erhielten, basierte auf etwas Separatem, das er als theoretische Übung tat – eine Anstrengung, eine bequemere Alternative zur Turing-Maschine zu definieren. Wie McCarthy später sagte:

Eine andere Möglichkeit zu zeigen, dass Lisp ordentlicher 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.... 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 praktischen Darstellung von Lisp-Programmen zu verwenden.

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

Das 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 es trotzdem. Das heißt, er kompilierte das eval in meinem Papier in [IBM] 704 Maschinencode, korrigierte Fehler und bewarb dies dann als Lisp-Interpreter, was es sicherlich war. Also hatte Lisp zu diesem Zeitpunkt im Wesentlichen die Form, die es heute hat....

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

Die kurze Erklärung, warum diese Sprache aus den 1950er Jahren nicht obsolet ist, ist, dass es sich nicht um Technologie, sondern um Mathematik handelte, 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 Allzwecksortieralgorithmus ist.

Es gibt noch eine weitere Sprache aus den 1950er Jahren, die überlebt hat: Fortran, und sie repräsentiert den entgegengesetzten Ansatz zum Sprachdesign. Lisp war ein Stück Theorie, das unerwartet zu einer Programmiersprache wurde. Fortran wurde absichtlich als Programmiersprache entwickelt, aber was wir heute als sehr low-level betrachten würden.

Fortran I, die 1956 entwickelte Sprache, war ein ganz anderes Kaliber als das heutige Fortran. Fortran I war im Grunde Assemblersprache mit Mathematik. In gewisser Weise war es weniger leistungsfähig als neuere Assemblersprachen; es gab zum Beispiel keine Unterroutinen, nur Sprünge. Das heutige Fortran ist heute wohl 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 konvergieren seitdem. Lisp begann leistungsstark und wurde über die nächsten zwanzig Jahre schnell. Die sogenannten Mainstream-Sprachen begannen schnell und wurden über die nächsten vierzig Jahre allmählich leistungsfähiger, bis sie jetzt, die fortschrittlichsten von ihnen, Lisp ziemlich nahe kommen. Nahe, aber ihnen fehlen immer noch ein paar Dinge....

Was Lisp anders machte

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

  1. Bedingte Ausdrücke. Ein bedingter Ausdruck ist eine If-Then-Else-Konstruktion. Wir nehmen diese heute als selbstverständlich hin, aber Fortran I hatte sie nicht. Es hatte nur ein bedingtes goto, das eng auf der zugrundeliegenden Maschinenanweisung basierte.

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

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

  4. 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 das Kopieren von Zeigern, nicht dessen, worauf sie zeigen.

  5. Garbage-Collection.

  6. 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.

Es war natürlich, diese Unterscheidung in Fortran I zu haben, weil man keine verschachtelten Anweisungen haben konnte. Und so, während man Ausdrücke für die Mathematik brauchte, gab es keinen Sinn, irgendetwas anderes einen Wert zurückgeben zu lassen, weil nichts darauf warten konnte.

Diese Einschränkung verschwand mit dem Aufkommen blockstrukturierter Sprachen, aber da war es zu spät. Die Unterscheidung zwischen Ausdrücken und Anweisungen war etabliert. Sie breitete sich von Fortran auf Algol und dann auf beide ihre Nachfolger aus.

  1. Ein Symboltyp. Symbole sind effektiv Zeiger auf Zeichenketten, die in einer Hashtabelle gespeichert sind. Man kann also die Gleichheit durch den Vergleich eines Zeigers testen, anstatt jedes Zeichen zu vergleichen.

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

  3. Die gesamte Sprache ist die ganze Zeit da. Es gibt keine wirkliche Unterscheidung zwischen Lesezeit, Kompilierzeit und Laufzeit. Man kann Code beim Lesen kompilieren oder ausführen, Code beim Kompilieren lesen oder ausführen und Code zur Laufzeit lesen oder kompilieren.

Das Ausführen von Code zur Lesezeit ermöglicht es Benutzern, die Syntax von Lisp neu zu programmieren; das Ausführen von Code zur Kompilierzeit ist die Grundlage von Makros; das Kompilieren zur Laufzeit ist die Grundlage der Verwendung 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 neu erfunden wurde.

Als Lisp zum ersten Mal erschien, waren diese Ideen weit von der gewöhnlichen Programmierpraxis entfernt, die weitgehend durch die verfügbare Hardware der späten 1950er Jahre bestimmt wurde. Im Laufe der Zeit hat sich die Standardsprache, verkörpert in einer Abfolge populärer Sprachen, allmählich in Richtung Lisp entwickelt. 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 Nummer 8 betrifft, so ist dies vielleicht das Interessanteste von allem. Ideen 8 und 9 wurden Lisp nur zufällig hinzugefügt, weil Steve Russell etwas implementierte, das McCarthy nie beabsichtigt hatte. Und doch erweisen sich diese Ideen als verantwortlich für sowohl das seltsame Aussehen von Lisp als auch für seine charakteristischsten Merkmale. Lisp sieht seltsam aus, nicht so sehr, weil es eine seltsame Syntax hat, sondern weil es keine Syntax hat; man drückt Programme direkt in den Parse-Bäumen aus, die im Hintergrund aufgebaut werden, wenn andere Sprachen geparst werden, und diese Bäume bestehen aus Listen, die Lisp-Datenstrukturen sind.

Das Ausdrücken der Sprache in ihren eigenen Datenstrukturen erweist sich als sehr mächtiges Merkmal. Ideen 8 und 9 zusammen bedeuten, dass man Programme schreiben kann, die Programme schreiben. Das mag wie eine bizarre Idee klingen, aber in Lisp ist es alltäglich. Die gebräuchlichste Methode dafür ist etwas namens Makro.

Der Begriff "Makro" bedeutet in Lisp nicht dasselbe wie in anderen Sprachen. Ein Lisp-Makro kann alles sein, von einer Abkürzung bis zu einem Compiler für eine neue Sprache. Wenn Sie Lisp wirklich verstehen oder einfach Ihren Programmierhorizont erweitern möchten, würde ich mehr darüber über Makros lernen.

Makros (im Lisp-Sinne) sind meines Wissens immer noch einzigartig für Lisp. Das liegt teilweise daran, dass man, um Makros zu haben, seine Sprache wahrscheinlich so seltsam aussehen lassen muss wie Lisp. Es mag auch daran liegen, dass, wenn man diesen letzten Schritt der Macht hinzufügt, man nicht mehr behaupten kann, eine neue Sprache erfunden zu haben, sondern nur einen neuen Dialekt von Lisp.

Ich erwähne das hauptsächlich als Scherz, aber es ist ziemlich wahr. Wenn man eine Sprache definiert, die car, cdr, cons, quote, cond, atom, eq und eine Notation für Listen-Ausdrücke hat, kann man den Rest von Lisp daraus aufbauen. Das ist tatsächlich die definierende Eigenschaft von Lisp: McCarthy gab Lisp diese Form, um dies zu erreichen.

Wo Sprachen wichtig sind

Angenommen, Lisp repräsentiert eine Art Grenze, der sich Mainstream-Sprachen asymptotisch nähern – bedeutet das, dass man es tatsächlich zum Schreiben von Software verwenden sollte? Wie viel verliert man, wenn man eine weniger leistungsfähige Sprache verwendet? Ist es nicht manchmal klüger, nicht am äußersten Rand der Innovation zu sein? Und ist Popularität nicht bis zu einem gewissen Grad eine Rechtfertigung für sich selbst? Hat der spitze Haar-Chef nicht zum Beispiel 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 viel ausmacht. In der Regel gilt: Je anspruchsvoller die Anwendung, desto mehr Vorteil erzielt man durch die Verwendung einer leistungsfähigen Sprache. Aber viele Projekte sind überhaupt nicht anspruchsvoll. Die meiste Programmierung besteht wahrscheinlich aus dem Schreiben kleiner "Glue-Programme", und für kleine "Glue-Programme" kann man jede Sprache verwenden, mit der man bereits vertraut ist und die gute Bibliotheken für das bietet, was man braucht. Wenn man nur Daten von einer Windows-Anwendung zur anderen übertragen muss, dann ja, verwenden Sie Visual Basic.

Man kann auch kleine "Glue-Programme" in Lisp schreiben (ich benutze es als Desktop-Rechner), aber der größte Gewinn für Sprachen wie Lisp liegt am anderen Ende des Spektrums, wo man anspruchsvolle Programme schreiben muss, um schwierige Probleme angesichts harten Wettbewerbs zu lösen. Ein gutes Beispiel ist das Airline-Fare-Suchprogramm, das ITA Software an Orbitz lizenziert. Diese Leute sind in einen Markt eingetreten, der bereits von zwei großen, etablierten Konkurrenten, Travelocity und Expedia, dominiert wurde, und scheinen sie technologisch einfach gedemütigt zu haben.

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

Zentripetale Kräfte

Ich sage nicht, dass die Verwendung ungewöhnlicher Technologien keine Kosten hat. Der spitze Haar-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 übertreiben.

Mir fallen drei Probleme ein, die bei der Verwendung weniger verbreiteter Sprachen auftreten 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 haben möglicherweise Schwierigkeiten, Programmierer einzustellen.

Wie groß ist jedes dieser Probleme? Die Bedeutung des ersten hängt davon ab, ob Sie das gesamte System kontrollieren. Wenn Sie Software schreiben, die auf dem Rechner eines entfernten Benutzers auf einem fehlerhaften, geschlossenen Betriebssystem (ich nenne keine Namen) laufen muss, kann es Vorteile haben, Ihre Anwendung in derselben Sprache wie das Betriebssystem zu schreiben. Aber wenn Sie das gesamte System kontrollieren und den Quellcode aller Teile haben, wie es ITA vermutlich tut, können Sie jede beliebige Sprache verwenden. Wenn Kompatibilitätsprobleme auftreten, können Sie sie selbst beheben.

Bei serverbasierten Anwendungen können Sie die fortschrittlichsten Technologien nutzen, und ich denke, das ist die Hauptursache für das, was Jonathan Erickson die "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-Anwendungen verwenden, sondern weil die Leute sie auf Servern verwenden. Und da sich Software vom Desktop auf Server verlagert (eine Zukunft, zu der selbst Microsoft resigniert zu sein scheint), wird es immer weniger Druck geben, mittelmäßige Technologien zu verwenden.

Was Bibliotheken angeht, so hängt ihre Bedeutung ebenfalls von der Anwendung ab. Für weniger anspruchsvolle Probleme kann die Verfügbarkeit von Bibliotheken die intrinsische Leistungsfähigkeit der Sprache überwiegen. Wo ist der Break-Even-Punkt? Schwer genau zu sagen, aber wo auch immer er ist, er liegt unterhalb von allem, was man wahrscheinlich als Anwendung bezeichnen würde. Wenn ein Unternehmen sich als Softwareunternehmen betrachtet und eine Anwendung schreibt, die eines seiner Produkte sein wird, dann wird sie wahrscheinlich mehrere Hacker beschäftigen und mindestens sechs Monate zum Schreiben benötigen. Bei einem Projekt dieser Größe überwiegen leistungsfähige Sprachen wahrscheinlich den Komfort bereits vorhandener Bibliotheken.

Die dritte Sorge des spitzen Haar-Chefs, die Schwierigkeit, Programmierer einzustellen, halte ich für ein rotes Hering. Wie viele Hacker müssen Sie schließlich einstellen? Sicherlich wissen wir inzwischen alle, dass Software am besten von Teams von weniger als zehn Personen entwickelt wird. Und Sie sollten keine Schwierigkeiten haben, Hacker in dieser Größenordnung für jede Sprache einzustellen, von der jemals jemand 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 verringert die Wahl einer leistungsfähigeren Sprache wahrscheinlich die Größe des Teams, das Sie benötigen, da (a) wenn Sie eine leistungsfähigere Sprache verwenden, Sie wahrscheinlich nicht so viele Hacker benötigen, und (b) Hacker, die in fortschrittlicheren Sprachen arbeiten, wahrscheinlich intelligenter sind.

Ich sage nicht, dass Sie keinen großen Druck erfahren werden, als "Standard" wahrgenommene Technologien zu verwenden. Bei Viaweb (jetzt Yahoo Store) haben wir bei VCs und potenziellen Käufern für Aufsehen gesorgt, indem wir Lisp verwendet haben. Aber wir haben auch für Aufsehen gesorgt, indem wir generische Intel-Boxen als Server anstelle von "industrietauglichen" Servern wie Suns verwendet haben, indem wir eine damals obskure Open-Source-Unix-Variante namens FreeBSD anstelle eines echten kommerziellen Betriebssystems wie Windows NT verwendet haben, indem wir einen angeblichen E-Commerce-Standard namens SET ignoriert haben, an den sich heute niemand mehr erinnert, und so weiter.

Sie können nicht zulassen, dass die Anzugträger technische Entscheidungen für Sie treffen. 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 die Software nicht schreiben können, die sie dazu brachte, uns kaufen zu wollen. Was ihnen wie eine Anomalie vorkam, war in Wirklichkeit Ursache und Wirkung.

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

Die Kosten des Durchschnitts

Wie viel verlieren Sie, wenn Sie eine weniger leistungsfähige Sprache verwenden? Dafür gibt es tatsächlich einige Daten.

Das bequemste Maß für die Leistung ist wahrscheinlich die Code-Größe. Der Sinn von Hochsprachen ist es, größere Abstraktionen zu bieten – größere Ziegelsteine, sozusagen, damit man nicht so viele braucht, um eine Mauer einer bestimmten Größe zu bauen. Je leistungsfähiger die Sprache, desto kürzer das Programm (nicht nur in Zeichen, sondern in einzelnen Elementen).

Wie ermöglicht eine leistungsfähigere Sprache das Schreiben kürzerer Programme? Eine Technik, die man anwenden kann, wenn die Sprache es zulässt, ist etwas namens Bottom-up-Programmierung. Anstatt Ihre Anwendung einfach in der Basissprache zu schreiben, bauen Sie auf der Basissprache eine Sprache zum Schreiben Ihrer Programme auf 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 funktionieren die meisten Komprimierungsalgorithmen so. Ein Bottom-up-Programm sollte auch einfacher zu ändern sein, da in vielen Fällen die Sprachebene gar nicht geändert werden muss.

Die Code-Größe ist wichtig, da die Zeit, die zum Schreiben eines Programms benötigt wird, hauptsächlich von seiner 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 Sie können dies nicht durch die Einstellung weiterer Leute umgehen, da über eine bestimmte Größe hinaus neue Mitarbeiter tatsächlich ein Nettoverlust sind. Fred Brooks beschrieb dieses Phänomen in seinem berühmten Buch The Mythical Man-Month, und alles, was ich gesehen habe, hat dazu tendiert, das zu bestätigen, was er gesagt hat.

Wie viel kürzer sind Ihre Programme also, wenn Sie sie in Lisp schreiben? Die meisten Zahlen, die ich für Lisp im Vergleich zu C gehört habe, liegen bei etwa 7-10x. Aber ein kürzlicher Artikel über ITA in New Architect Magazin sagte, dass "eine Zeile Lisp 20 Zeilen C ersetzen kann", und da dieser Artikel voller Zitate des Präsidenten von ITA war, gehe ich davon aus, dass sie diese Zahl von ITA haben. Wenn ja, können wir ihr Glauben schenken; die Software von ITA enthält viel C und C++ sowie Lisp, also sprechen sie aus Erfahrung.

Ich vermute, dass diese Vielfachen nicht einmal konstant sind. Ich glaube, sie steigen, wenn man schwierigere Probleme hat und auch, wenn man intelligentere Programmierer hat. Ein wirklich guter Hacker kann mehr aus besseren Werkzeugen herausholen.

Als ein Datenpunkt auf der Kurve, jedenfalls, wenn man ITA konkurrieren und seine Software in C schreiben wollte, könnten sie 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 nur drei Monate für die Entwicklung von etwas Neuem aufwenden würden, es fünf Jahre dauern würde, bis Sie es auch hätten.

Und wissen Sie was? Das ist das Best-Case-Szenario. Wenn man über Code-Größen-Verhältnisse spricht, nimmt man implizit an, dass man das Programm tatsächlich in der schwächeren Sprache schreiben kann. Aber tatsächlich gibt es Grenzen dafür, was Programmierer tun können. Wenn man versucht, ein schwieriges Problem mit einer zu low-level Sprache zu lösen, erreicht man einen Punkt, an dem einfach zu viel auf einmal im Kopf behalten werden muss.

Wenn ich also sage, dass es dem imaginären Konkurrenten von ITA fünf Jahre dauern würde, etwas zu duplizieren, das ITA in drei Monaten in Lisp schreiben könnte, meine ich fünf Jahre, wenn nichts schiefgeht. Tatsächlich ist in den meisten Unternehmen jedes Entwicklungsprojekt, das fünf Jahre dauern würde, wahrscheinlich nie fertig.

Ich gebe zu, das ist ein Extremfall. Die Hacker von ITA scheinen ungewöhnlich intelligent zu sein, und C ist eine ziemlich low-level Sprache. Aber in einem wettbewerbsorientierten Markt würde selbst ein Unterschied von zwei oder drei zu eins ausreichen, um zu garantieren, dass Sie immer im Rückstand sein werden.

Ein Rezept

Das ist die Art von Möglichkeit, über die der spitze Haar-Chef nicht einmal nachdenken möchte. Und so tun es die meisten von ihnen nicht. Denn, wissen Sie, wenn es darauf ankommt, ist es dem spitzen Haar-Chef egal, ob sein Unternehmen den Arsch versohlt bekommt, solange niemand beweisen kann, dass es seine Schuld ist. Der sicherste Plan für ihn persönlich ist es, nahe am Zentrum der Herde zu bleiben.

Innerhalb großer Organisationen wird der Begriff "Industry Best Practice" verwendet, um diesen Ansatz zu beschreiben. Sein Zweck ist es, den spitzen Haar-Chef von der Verantwortung zu entbinden: Wenn er etwas wählt, das "Industry Best Practice" ist, und das Unternehmen verliert, kann er nicht beschuldigt werden. Er hat nicht gewählt, die Industrie hat es getan.

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

Technologie sollte oft Spitzentechnologie sein. Bei Programmiersprachen, wie Erann Gat bemerkt hat, bekommt man mit "Industry Best Practice" nicht das Beste, sondern nur den Durchschnitt. Wenn eine Entscheidung dazu führt, dass man Software mit einem Bruchteil der Geschwindigkeit aggressiverer Konkurrenten entwickelt, ist "Best Practice" ein Euphemismus.

Hier haben wir also zwei Informationen, die meiner Meinung nach sehr wertvoll sind. Tatsächlich weiß ich das aus eigener Erfahrung. Nummer 1, Sprachen unterscheiden sich in ihrer Leistung. Nummer 2, die meisten Manager ignorieren dies bewusst. Zwischen ihnen sind diese beiden Fakten buchstäblich ein Rezept, um Geld zu verdienen. ITA ist ein Beispiel für dieses Rezept in Aktion. Wenn Sie in einem Softwaregeschäft 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, bis die spitzen Haar-Chefs Ihrer Konkurrenten zum Durchschnitt zurückkehren.


Anhang: Leistung

Als Illustration dessen, was ich unter der relativen Leistung von Programmiersprachen verstehe, betrachten wir das folgende Problem. Wir möchten eine Funktion schreiben, die Akkumulatoren generiert – eine Funktion, die eine Zahl n nimmt und eine Funktion zurückgibt, die eine andere Zahl i nimmt und n plus i zurückgibt.

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

In Common Lisp wäre dies (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 man in Perl Parameter manuell extrahieren muss.

In Smalltalk ist der Code etwas länger als in Lisp foo: n |s| s := n. ^[:i| s := s+i. ], weil, obwohl im Allgemeinen lexikalische Variablen funktionieren, man keine Zuweisung zu einem Parameter machen kann, also muss man eine neue Variable s erstellen.

In Javascript ist das Beispiel wieder etwas länger, da Javascript die Unterscheidung zwischen Anweisungen und Ausdrücken beibehält, sodass Sie explizite return-Anweisungen zum Zurückgeben von Werten benötigen: function foo(n) { return function (i) { return n += i } } (Um fair zu sein, Perl behält diese Unterscheidung ebenfalls bei, behandelt sie aber auf typische Perl-Weise, indem es erlaubt, returns wegzulassen).

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 speichern. Und obwohl Python einen Funktionstyp hat, gibt es keine literale Darstellung dafür (es sei denn, der Körper ist nur ein einzelner Ausdruck), daher müssen Sie eine benannte Funktion erstellen, die zurückgegeben wird. Hier ist, was Sie am Ende erhalten: def foo(n): s = [n] def bar(i): s[0] += i return s[0] return bar Python-Benutzer könnten berechtigterweise 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 vollständig 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 Bereichen definiert sind) simulieren, indem man eine Klasse mit einer Methode und einem Feld für jede Variable aus einem umschließenden Bereich definiert. Dies zwingt den Programmierer zu der Art von Code-Analyse, die vom Compiler in einer Sprache mit voller Unterstützung für lexikalischen Geltungsbereich durchgeführt würde, und es funktioniert nicht, wenn mehr als eine Funktion auf dieselbe Variable verweist, aber es reicht in einfachen Fällen wie diesem.

Python-Experten scheinen sich einig zu sein, dass dies der bevorzugte Weg ist, das Problem in Python zu lösen, indem sie entweder schreiben 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 nehme diese auf, weil ich nicht möchte, dass Python-Befürworter sagen, ich würde die Sprache falsch darstellen, aber beide scheinen mir komplexer zu sein als die erste Version. Sie tun dasselbe, richten einen separaten Ort ein, 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 kleiner Hack zu sein.

Im Wettstreit zwischen Perl und Python scheint die Behauptung der Python-Hacker zu sein, dass Python eine elegantere Alternative zu Perl sei, 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 in diesem Vortrag erwähnten Sprachen – Fortran, C, C++, Java und Visual Basic – ist nicht klar, ob man dieses Problem tatsächlich lösen kann. Ken Anderson sagt, dass der folgende Code dem am nächsten kommt, was in Java möglich ist:

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, da es nur für Ganzzahlen funktioniert. Nach vielen E-Mail-Austauschen mit Java-Hackern würde ich sagen, dass das Schreiben einer ordnungsgemäß 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 daran interessiert, sie zu sehen, aber ich persönlich habe aufgegeben.

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

Das klingt wie ein Witz, aber es passiert in großen Programmierprojekten so oft in unterschiedlichem Maße, dass es einen Namen für das Phänomen gibt, Greenspun's Tenth Rule:

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

Wenn man versucht, ein schwieriges Problem zu lösen, stellt sich nicht die Frage, ob man eine ausreichend leistungsfähige Sprache verwendet, sondern ob man (a) eine leistungsfähige Sprache verwendet, (b) einen De-facto-Interpreter dafür schreibt oder (c) selbst zu einem menschlichen Compiler dafür wird. Wir sehen bereits, dass dies im Python-Beispiel beginnt, wo wir effektiv den Code simulieren, den ein Compiler zur Implementierung einer lexikalischen Variable generieren würde.

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

Anmerkungen

  • Die IBM 704 CPU war etwa kühlschrankgroß, aber viel schwerer. Die CPU wog 3150 Pfund, und die 4K RAM befanden sich 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 1962 auch das erste (digitale) Computerspiel, Spacewar.

  • Wenn Sie einen spitzen Haar-Chef dazu bringen wollen, Ihnen zu erlauben, Software in Lisp zu schreiben, könnten Sie ihm sagen, 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 "Industry Best Practice" am JPL inspirierte mich, diesen allgemein falsch angewendeten Begriff anzusprechen.

  • Peter Norvig fand, dass 16 der 23 Patterns in Design Patterns in Lisp "unsichtbar oder einfacher" waren http://www.norvig.com/design-patterns/.

  • Vielen Dank an die vielen Leute, die meine Fragen zu verschiedenen Sprachen beantwortet und/oder Entwürfe davon 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.

Verwandt:

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

Er löste auch eine ausführliche und oft nützliche Diskussion auf der LL1 Mailingliste aus. Sehen Sie sich insbesondere die E-Mail von Anton van Straaten über semantische Komprimierung an.

Einige der E-Mails auf LL1 brachten mich dazu, tiefer in das Thema Sprachleistung einzudringen in Succinctness is Power.

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

Japanische Übersetzung, Spanische Übersetzung, Chinesische Übersetzung