RACHE DER NERDS
OriginalMai 2002
"Wir hatten es auf die C++-Programmierer abgesehen. Wir haben es geschafft, viele von ihnen etwa bis zur Hälfte zu Lisp zu ziehen."
- Guy Steele, Mitautor der Java-Spezifikation
In der Softwarebranche gibt es einen fortwährenden Kampf zwischen den akademischen Köpfen und einer anderen ebenso formidable Kraft, den spitzhaarigen Chefs. Jeder weiß, wer der spitzhaarige Chef ist, oder? Ich denke, die meisten Menschen in der Technologiewelt erkennen nicht nur diese Cartoonfigur, sondern kennen auch die tatsächliche Person in ihrem Unternehmen, auf der sie basiert.
Der spitzhaarige Chef kombiniert auf wunderbare Weise zwei Eigenschaften, die für sich genommen häufig sind, aber selten zusammen auftreten: (a) Er weiß überhaupt nichts über Technologie, und (b) er hat sehr starke Meinungen darüber.
Nehmen wir an, Sie müssen ein Stück Software schreiben. Der spitzhaarige Chef hat keine Ahnung, wie diese Software funktionieren muss, und kann eine Programmiersprache von einer anderen nicht unterscheiden, und doch weiß er, in welcher Sprache Sie es schreiben sollten. Genau. Er denkt, Sie sollten es in Java schreiben.
Warum denkt er das? Lassen Sie uns einen Blick in das Gehirn des spitzhaarigen Chefs werfen. Was er denkt, ist ungefähr Folgendes: Java ist ein Standard. Ich weiß, dass es ein Standard sein muss, denn ich lese ständig darüber in der Presse. Da es ein Standard ist, werde ich keine Probleme bekommen, wenn ich es benutze. Und das bedeutet auch, dass es immer viele Java-Programmierer geben wird, sodass ich sie leicht ersetzen kann, wenn die Programmierer, die jetzt für mich arbeiten, kündigen, wie Programmierer, die für mich arbeiten, mysteröserweise immer tun.
Nun, das klingt nicht so unvernünftig. Aber es basiert alles auf einer unausgesprochenen Annahme, und diese Annahme stellt sich als falsch heraus. Der spitzhaarige Chef glaubt, dass alle Programmiersprachen ziemlich gleichwertig sind. Wenn das wahr wäre, hätte er genau recht. Wenn Sprachen alle gleichwertig sind, dann verwenden Sie natürlich die Sprache, die alle anderen verwenden.
Aber nicht alle Sprachen sind gleichwertig, und ich denke, ich kann Ihnen das beweisen, ohne auch nur auf die Unterschiede zwischen ihnen einzugehen. Wenn Sie den spitzhaarigen Chef 1992 gefragt hätten, in welcher Sprache Software geschrieben werden sollte, hätte er mit so wenig Zögern geantwortet, wie er es heute tut. Software sollte in C++ geschrieben werden. Aber wenn Sprachen alle gleichwertig sind, warum sollte sich dann die Meinung des spitzhaarigen Chefs jemals ändern? Tatsächlich, warum sollten die Entwickler von Java sich überhaupt die Mühe gemacht haben, eine neue Sprache zu schaffen?
Vermutlich, wenn Sie eine neue Sprache schaffen, 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. Also haben Sie es: Sprachen sind nicht alle gleichwertig. Wenn Sie den Pfad durch das Gehirn des spitzhaarigen Chefs zu Java und dann zurück durch die Geschichte von Java zu seinen Ursprüngen verfolgen, halten Sie am Ende eine Idee in der Hand, die der Annahme widerspricht, mit der Sie begonnen haben.
Also, wer hat recht? James Gosling oder der spitzhaarige Chef? Nicht überraschend hat Gosling recht. Einige Sprachen sind besser, für bestimmte Probleme, als andere. Und wissen Sie, das wirft einige interessante Fragen auf. Java wurde entwickelt, um besser zu sein, für bestimmte Probleme, 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 anfangen, diese Frage zu betrachten, haben Sie eine echte Dose Würmer geöffnet. Wenn der spitzhaarige Chef über das Problem in seiner vollen Komplexität nachdenken müsste, würde sein Gehirn explodieren. Solange er alle Sprachen als gleichwertig betrachtet, muss er nur die auswählen, die anscheinend den meisten Schwung hat, und da das mehr eine Frage der Mode als der Technologie ist, kann sogar er wahrscheinlich die richtige Antwort finden. Aber wenn Sprachen variieren, muss er plötzlich zwei gleichzeitige Gleichungen lösen und versuchen, ein optimales Gleichgewicht zwischen zwei Dingen zu finden, von denen er nichts weiß: der relativen Eignung der zwanzig oder so führenden Sprachen für das Problem, das er lösen muss, und den Chancen, Programmierer, Bibliotheken usw. für jede zu finden. Wenn das auf der anderen Seite der Tür steht, ist es kein Wunder, dass der spitzhaarige 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 die Idee so weit verbreitet ist. Es ist eine bequeme Idee.
Wir wissen, dass Java ziemlich gut sein muss, denn es ist die coole, neue Programmiersprache. Oder etwa nicht? Wenn Sie die Welt der Programmiersprachen aus der Ferne betrachten, sieht es so aus, als wäre Java das Neueste. (Aus ausreichender Entfernung sieht man nur das große, blinkende Plakat, das von Sun bezahlt wurde.) Aber wenn Sie sich diese Welt aus der Nähe ansehen, stellen Sie fest, dass es Abstufungen in der 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 von Perl generiert. Ich glaube nicht, dass Sie diese Jungs mit Java Server Pages finden würden. Aber es gibt eine andere, neuere Sprache namens Python, deren Benutzer dazu neigen, auf Perl herabzusehen, und mehr warten im Hintergrund.
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 ist zunehmend mehr wie Lisp. Python kopiert sogar Funktionen, die viele Lisp-Hacker als Fehler betrachten. Sie könnten einfache Lisp-Programme zeilenweise in Python übersetzen. Es ist 2002, und Programmiersprachen haben fast 1958 eingeholt.
Aufholen mit Mathematik
Was ich meine, ist, dass Lisp 1958 erstmals von John McCarthy entdeckt wurde, und beliebte Programmiersprachen erst jetzt die Ideen einholen, die er damals entwickelt hat.
Wie könnte das wahr sein? Ist Computertechnologie nicht etwas, das sich sehr schnell ändert? Ich meine, 1958 waren Computer Kühlschrank-große Ungeheuer mit der Rechenleistung einer Armbanduhr. Wie könnte eine so alte Technologie überhaupt relevant sein, geschweige denn überlegen gegenüber den neuesten Entwicklungen?
Ich sage Ihnen, wie. Es liegt daran, dass Lisp nicht wirklich als Programmiersprache entworfen wurde, zumindest nicht im Sinne, wie wir es heute meinen. Was wir unter einer Programmiersprache verstehen, ist etwas, das wir verwenden, um einem Computer zu sagen, was er tun soll. McCarthy hatte schließlich die Absicht, eine Programmiersprache in diesem Sinne zu entwickeln, aber das Lisp, das wir tatsächlich erhalten haben, basierte auf etwas Separatem, das er als theoretische Übung durchgeführt hat – ein Versuch, 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 zu denken, dass sie in der Praxis verwendet werden würde, um Lisp-Programme auszudrücken.
Was dann geschah, war, dass Steve Russell, einer von McCarthys Doktoranden, irgendwann Ende 1958 diese Definition von eval ansah und erkannte, dass, wenn er sie in Maschinensprache übersetzen würde, das Ergebnis ein Lisp-Interpreter wäre.
Das war damals eine große Überraschung. Hier ist, was McCarthy später in einem Interview darüber sagte:
Steve Russell sagte: Schau, warum programmiere ich nicht dieses eval..., und ich sagte zu ihm: Ho, ho, du verwechselst Theorie mit Praxis, dieses eval ist zum Lesen gedacht, nicht zum Rechnen. Aber er machte einfach weiter und tat es. Das heißt, er kompilierte das eval in meinem Papier in [IBM] 704-Maschinencode, behob Fehler und warb dann damit als Lisp-Interpreter, was es sicherlich war. So hatte Lisp zu diesem Zeitpunkt im Wesentlichen die Form, die es heute hat....
Plötzlich, innerhalb weniger Wochen, fand McCarthy seine theoretische Übung in eine tatsächliche Programmiersprache verwandelt – und eine leistungsfähigere, als er beabsichtigt hatte.
Die kurze Erklärung, warum diese Sprache aus den 1950er Jahren nicht obsolet ist, ist, dass es nicht Technologie, sondern Mathematik war, und Mathematik wird nicht alt. Das Richtige, um Lisp zu vergleichen, ist nicht die Hardware der 1950er Jahre, sondern zum Beispiel der Quicksort-Algorithmus, der 1960 entdeckt wurde und immer noch die schnellste allgemeine Sortierung ist.
Es gibt eine andere Sprache, die aus den 1950er Jahren überlebt hat, Fortran, und sie repräsentiert den gegenteiligen Ansatz zum Sprachenentwurf. Lisp war ein Stück Theorie, das unerwartet in eine Programmiersprache verwandelt wurde. Fortran wurde absichtlich als Programmiersprache entwickelt, aber was wir jetzt als eine 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 ziemlich genau 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 arguably näher an Lisp als an Fortran I.
Lisp und Fortran waren die Stämme zweier separater evolutionärer Bäume, einer verwurzelt in Mathematik und einer verwurzelt in Maschinenarchitektur. Diese beiden Bäume nähern sich seitdem an. Lisp begann leistungsfähig und wurde in den nächsten zwanzig Jahren schnell. So genannte Mainstream-Sprachen begannen schnell und wurden in den nächsten vierzig Jahren allmählich leistungsfähiger, bis jetzt die fortschrittlichsten von ihnen ziemlich nah an Lisp sind. Nah, aber sie fehlen immer noch ein paar Dinge....
Was Lisp anders machte
Als es erstmals entwickelt wurde, verkörperte Lisp neun neue Ideen. Einige davon halten wir jetzt für selbstverständlich, andere sind nur in fortgeschritteneren 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 halten diese jetzt für selbstverständlich, aber Fortran I hatte sie nicht. Es hatte nur einen bedingten Sprung, der eng auf der zugrunde liegenden Maschinenanweisung basierte.
Ein Funktionstyp. In Lisp sind Funktionen ein Datentyp wie Ganzzahlen oder Strings. 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 haben Typen, nicht Variablen, und das Zuweisen oder Binden von Variablen bedeutet, Zeiger zu kopieren, nicht das, 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. 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 Sie Anweisungen nicht verschachteln konnten. Und während Sie Ausdrücke für Mathematik benötigten, gab es keinen Grund, dass irgendetwas anderes einen Wert zurückgibt, weil es nichts geben konnte, das darauf wartete.
Diese Einschränkung verschwand mit dem Aufkommen blockstrukturierter Sprachen, aber bis dahin war es zu spät. Die Unterscheidung zwischen Ausdrücken und Anweisungen war verankert. Sie verbreitete sich von Fortran über Algol und dann zu beiden ihren Nachkommen.
Ein Symboltyp. Symbole sind effektiv Zeiger auf Strings, die in einer Hash-Tabelle gespeichert sind. So können Sie die Gleichheit testen, indem Sie einen Zeiger vergleichen, 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 Lesezeit, Kompilierzeit und Laufzeit. Sie können Code kompilieren oder ausführen, während Sie lesen, Code lesen oder ausführen, während Sie kompilieren, 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, mithilfe von s-Ausdrücken zu kommunizieren, einer Idee, die kürzlich als XML neu erfunden wurde.
Als Lisp erstmals erschien, waren diese Ideen weit entfernt von der gewöhnlichen Programmierpraxis, die größtenteils von der Hardware diktiert wurde, die in den späten 1950er Jahren verfügbar war. Im Laufe der Zeit hat sich die Standardsprache, verkörpert in einer Reihe von beliebten 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 anscheinend keine Syntax gibt.
Was Nummer 8 betrifft, so könnte dies die interessanteste der Gruppe sein. Ideen 8 und 9 wurden nur zufällig Teil von Lisp, weil Steve Russell etwas implementierte, das McCarthy nie beabsichtigt hatte, implementiert zu werden. Und doch sind diese Ideen dafür verantwortlich, dass Lisp sowohl ein seltsames Aussehen als auch seine markantesten Merkmale hat. Lisp sieht seltsam aus, nicht so sehr, weil es eine seltsame Syntax hat, sondern weil es keine Syntax hat; Sie drücken Programme direkt in den Parse-Bäumen aus, die im Hintergrund 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, stellt sich als ein sehr leistungsfähiges Merkmal heraus. Ideen 8 und 9 zusammen bedeuten, dass Sie Programme schreiben können, die Programme schreiben. Das mag wie eine bizarre Idee erscheinen, aber es ist eine alltägliche Sache in Lisp. Der gebräuchlichste Weg, dies zu tun, ist mit etwas, das man 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 zu einem Compiler für eine neue Sprache. Wenn Sie Lisp wirklich verstehen oder einfach nur Ihre Programmierhorizonte erweitern möchten, würde ich mehr über Makros lernen.
Makros (im Lisp-Sinn) sind, soweit ich weiß, immer noch einzigartig für Lisp. Das liegt teilweise daran, dass Sie, um Makros zu haben, Ihre Sprache wahrscheinlich so seltsam wie Lisp aussehen lassen müssen. Es könnte auch daran liegen, dass, wenn Sie diesen letzten Zuwachs an Macht hinzufügen, Sie nicht mehr behaupten können, eine neue Sprache erfunden zu haben, sondern nur einen neuen Dialekt von Lisp.
Ich erwähne dies hauptsächlich als Scherz, aber es ist ganz wahr. Wenn Sie eine Sprache definieren, die car, cdr, cons, quote, cond, atom, eq und eine Notation für Funktionen, die als Listen ausgedrückt werden, hat, dann können Sie den Rest von Lisp daraus aufbauen. Das ist in der Tat die definierende Eigenschaft von Lisp: Es war, um dies zu ermöglichen, dass McCarthy Lisp die Form gab, die es hat.
Wo Sprachen wichtig sind
Angenommen, Lisp stellt tatsächlich eine Art Grenze dar, der die Mainstream-Sprachen asymptotisch näher kommen – 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 nicht manchmal weiser, nicht am äußersten Rand der Innovation zu sein? Und ist Popularität nicht bis zu einem gewissen Grad ihre eigene Rechtfertigung? Hat der spitzhaarige Chef nicht recht, wenn er zum Beispiel 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. Im Allgemeinen gilt: Je anspruchsvoller die Anwendung, desto mehr Nutzen ziehen Sie aus der Verwendung einer leistungsfähigen Sprache. Aber viele Projekte sind überhaupt nicht anspruchsvoll. Die meiste Programmierung besteht wahrscheinlich darin, kleine Klebeprogramme zu schreiben, und für kleine 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 sicher Visual Basic.
Sie können auch kleine Klebeprogramme in Lisp schreiben (ich benutze es als Desktop-Rechner), aber der größte Vorteil für Sprachen wie Lisp liegt am anderen Ende des Spektrums, wo Sie komplexe Programme schreiben müssen, um schwierige Probleme im Angesicht harter Konkurrenz zu lösen. Ein gutes Beispiel ist das Flugpreissuchprogramm, das ITA Software an Orbitz lizenziert. Diese Jungs traten in einen Markt ein, der bereits von zwei großen, etablierten Wettbewerbern, Travelocity und Expedia, dominiert wurde, und scheinen sie technologisch einfach gedemütigt zu haben.
Der Kern der Anwendung von ITA ist ein 200.000 Zeilen umfassendes Common Lisp-Programm, das viele Größenordnungen mehr Möglichkeiten durchsucht als ihre Wettbewerber, die anscheinend immer noch Programmiertechniken aus der Mainframe-Ära verwenden. (Obwohl ITA auch in gewissem Sinne eine Programmiersprache aus der Mainframe-Ära verwendet.) Ich habe noch nie einen der Codes von ITA gesehen, aber laut einem ihrer besten Hacker verwenden sie viele Makros, und ich bin nicht überrascht, das zu hören.
Zentripetalkräfte
Ich sage nicht, dass es keine Kosten gibt, wenn man ungewöhnliche Technologien verwendet. Der spitzhaarige Chef liegt nicht ganz falsch, sich darüber Sorgen zu machen. Aber weil er die Risiken nicht versteht, neigt er dazu, sie zu übertreiben.
Ich kann an drei Probleme denken, die durch die Verwendung weniger gängiger 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 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 dem Computer 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 ITA es vermutlich tut, können Sie jede Sprache verwenden, die Sie möchten. Wenn eine Inkompatibilität auftritt, können Sie sie selbst beheben.
In serverbasierten Anwendungen können Sie die fortschrittlichsten Technologien verwenden, und ich denke, das ist die Hauptursache für das, was Jonathan Erickson als die "Renaissance der Programmiersprachen" bezeichnet. Deshalb hören wir überhaupt von neuen Sprachen wie Perl und Python. Wir hören nicht von diesen Sprachen, weil die Leute sie verwenden, um Windows-Apps zu schreiben, sondern weil die Leute sie auf Servern verwenden. Und während Software vom Desktop auf Server verschoben wird (eine Zukunft, mit der selbst Microsoft sich abgefunden zu haben scheint), wird der Druck, Technologien der Mittelklasse zu verwenden, immer geringer.
Was die Bibliotheken betrifft, so hängt ihre Bedeutung ebenfalls 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-Punkt? Schwer genau zu sagen, aber wo auch immer er ist, er liegt unterhalb von allem, was Sie wahrscheinlich als Anwendung bezeichnen würden. Wenn ein Unternehmen sich als im Softwaregeschäft befindlich betrachtet und eine Anwendung schreibt, die eines ihrer Produkte sein wird, dann wird es wahrscheinlich mehrere Hacker involvieren und mindestens sechs Monate in Anspruch nehmen. In einem Projekt dieser Größe beginnen leistungsfähige Sprachen wahrscheinlich, den Komfort vorgefertigter Bibliotheken zu überwiegen.
Die dritte Sorge des spitzhaarigen Chefs, die Schwierigkeit, Programmierer einzustellen, halte ich für einen roten Hering. Wie viele Hacker müssen Sie schließlich einstellen? Sicherlich wissen wir alle mittlerweile, dass Software am besten von Teams von weniger als zehn Personen entwickelt wird. Und Sie sollten keine Schwierigkeiten haben, Hacker in diesem Maßstab für jede Sprache zu finden, von der irgendjemand jemals gehört hat. Wenn Sie keine zehn Lisp-Hacker finden können, dann ist Ihr Unternehmen wahrscheinlich in der falschen Stadt für die Softwareentwicklung.
Tatsächlich verringert die Wahl einer leistungsfähigeren Sprache wahrscheinlich die Größe des Teams, das Sie benötigen, weil (a) wenn Sie eine leistungsfähigere Sprache verwenden, Sie wahrscheinlich nicht so viele Hacker benötigen, und (b) Hacker, die in fortgeschritteneren Sprachen arbeiten, wahrscheinlich intelligenter sind.
Ich sage nicht, dass Sie nicht viel Druck verspüren werden, Technologien zu verwenden, die als "Standard" angesehen werden. Bei Viaweb (jetzt Yahoo Store) haben wir bei VCs 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 verwendet haben, anstatt "industriestarke" Server wie Suns, 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 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 kein Lisp 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 erschien, war in der Tat Ursache und Wirkung.
Wenn Sie ein Startup gründen, gestalten Sie Ihr Produkt nicht so, dass es VCs oder potenziellen Käufern gefällt. Gestalten Sie Ihr Produkt so, dass es den Benutzern gefällt. Wenn Sie die Benutzer gewinnen, wird alles andere folgen. Und wenn nicht, wird es niemanden interessieren, wie tröstlich orthodox Ihre Technologieentscheidungen waren.
Die Kosten des Durchschnitts
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 die Leistung ist wahrscheinlich Codegröße. Der Sinn von Hochsprachen ist es, Ihnen größere Abstraktionen zu geben – größere Ziegel, sozusagen, damit Sie nicht so viele benötigen, um eine Wand einer bestimmten Größe zu bauen. Je leistungsfähiger die Sprache, desto kürzer das Programm (nicht nur in Zeichen, natürlich, sondern in unterschiedlichen Elementen).
Wie ermöglicht eine leistungsfähigere Sprache, dass Sie kürzere Programme schreiben? Eine Technik, die Sie verwenden können, wenn die Sprache es Ihnen erlaubt, 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 ganzes Programm in der Basissprache geschrieben hätten – in der Tat ist dies, wie die meisten Kompressionsalgorithmen funktionieren. Ein Bottom-up-Programm sollte auch einfacher zu modifizieren sein, weil in vielen Fällen die Sprachebene sich überhaupt nicht ändern muss.
Die Codegröße ist wichtig, weil die Zeit, die benötigt wird, um ein Programm zu schreiben, 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 umgehen, indem Sie mehr Leute einstellen, denn über eine bestimmte Größe hinaus sind neue Einstellungen tatsächlich ein Nettoverlust. 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 sagte.
Wie viel kürzer sind Ihre Programme, wenn Sie sie in Lisp schreiben? Die meisten Zahlen, die ich für Lisp im Vergleich zu C gehört habe, lagen beispielsweise bei etwa 7-10x. Aber ein kürzlich erschienener Artikel über ITA in der Zeitschrift New Architect sagte, dass "eine Zeile Lisp 20 Zeilen C ersetzen kann", und da dieser Artikel voller Zitate von ITAs Präsident war, nehme ich an, dass sie diese Zahl von ITA erhalten haben. Wenn dem so ist, können wir etwas Vertrauen in sie setzen; die Software von ITA umfasst viel C und C++ sowie Lisp, sodass sie aus Erfahrung sprechen.
Ich vermute, dass diese Vielfachen nicht einmal konstant sind. Ich denke, sie steigen, wenn Sie mit schwierigeren Problemen konfrontiert sind und auch, wenn Sie intelligentere Programmierer haben. Ein wirklich guter Hacker kann mehr aus besseren Werkzeugen herausholen.
Als ein Datenpunkt auf der Kurve, jedenfalls, wenn Sie mit ITA konkurrieren würden und sich entscheiden, Ihre Software in C zu schreiben, wären sie in der Lage, Software zwanzigmal schneller zu 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 sie nur drei Monate damit verbringen würden, etwas Neues zu entwickeln, würde es fünf Jahre dauern, bis Sie es auch hätten.
Und wissen Sie was? Das ist das beste Szenario. Wenn Sie über Codegrößenverhältnisse sprechen, setzen Sie implizit voraus, dass Sie das Programm tatsächlich in der schwächeren Sprache schreiben können. Aber in der Tat gibt es Grenzen dafür, was Programmierer tun können. Wenn Sie versuchen, ein schwieriges Problem mit einer Sprache zu lösen, die zu niedrigstufig ist, erreichen Sie einen Punkt, an dem es einfach zu viel ist, um es gleichzeitig im Kopf zu behalten.
Wenn ich also sage, dass es fünf Jahre dauern würde, bis der imaginäre Konkurrent von ITA etwas dupliziert, das ITA in drei Monaten in Lisp schreiben könnte, meine ich fünf Jahre, wenn nichts schiefgeht. Tatsächlich ist es so, wie die Dinge in den meisten Unternehmen laufen, dass jedes Entwicklungsprojekt, das fünf Jahre dauern würde, wahrscheinlich nie abgeschlossen wird.
Ich gebe zu, dass dies ein extremer Fall ist. Die Hacker von ITA scheinen ungewöhnlich intelligent zu sein, und C ist eine ziemlich niedrigstufige Sprache. Aber in einem wettbewerbsintensiven Markt würde selbst ein Unterschied von zwei oder drei zu eins ausreichen, um sicherzustellen, dass Sie immer im Rückstand wären.
Ein Rezept
Das ist die Art von Möglichkeit, über die der spitzhaarige Chef nicht einmal nachdenken möchte. Und so tun die meisten von ihnen das nicht. Denn wissen Sie, wenn es darauf ankommt, ist dem spitzhaarigen Chef egal, ob sein Unternehmen geschlagen wird, solange niemand beweisen kann, dass es seine Schuld ist. Der sicherste Plan für ihn persönlich ist es, sich eng an das Zentrum der Herde zu halten.
Innerhalb großer Organisationen wird der Ausdruck verwendet, um diesen Ansatz zu beschreiben, "Best Practices der Branche". Ihr Zweck ist es, den spitzhaarigen Chef von der Verantwortung zu schützen: Wenn er etwas wählt, das "Best Practice der Branche" ist, und das Unternehmen verliert, kann ihm kein Vorwurf 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 es grob bedeutet, ist tu nichts Seltsames. Und in der Buchhaltung ist das wahrscheinlich eine gute Idee. Die Begriffe "bahnbrechend" und "Buchhaltung" klingen nicht gut zusammen. Aber wenn Sie dieses Kriterium in Entscheidungen über Technologie importieren, beginnen Sie, die falschen Antworten zu erhalten.
Technologie sollte oft bahnbrechend sein. In Programmiersprachen, wie Erann Gat darauf hingewiesen hat, bringt Ihnen das, was "Best Practice der Branche" tatsächlich bedeutet, nicht das Beste, sondern lediglich das Durchschnittliche. Wenn eine Entscheidung dazu führt, dass Sie Software mit einem Bruchteil der Geschwindigkeit aggressiverer Wettbewerber entwickeln, ist "Best Practice" ein irreführender Begriff.
Hier haben wir also 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 dies absichtlich. Zwischen diesen beiden Fakten liegt buchstäblich ein Rezept, um Geld zu verdienen. ITA ist ein Beispiel für dieses Rezept in Aktion. Wenn Sie im 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 darauf, dass die spitzhaarigen Chefs Ihrer Wettbewerber zur Mitte zurückkehren.
Anhang: Leistung
Als Illustration dessen, was ich über die relative Leistung von Programmiersprachen meine, betrachten Sie folgendes Problem. Wir wollen eine Funktion schreiben, die Akkumulatoren generiert – eine Funktion, die eine Zahl n entgegennimmt und eine Funktion zurückgibt, die eine andere Zahl i entgegennimmt und n um i erhöht zurückgibt.
(Das ist um erhöht, 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}
}
was mehr Elemente hat als die Lisp-Version, weil Sie in Perl die 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. ]
weil, obwohl im Allgemeinen lexikalische Variablen funktionieren, Sie keine Zuweisung an einen Parameter vornehmen können, sodass Sie eine neue Variable s erstellen müssen.
In Javascript ist das Beispiel wieder etwas länger, weil Javascript die Unterscheidung zwischen Anweisungen und Ausdrücken beibehält, sodass Sie explizite Rückgabewerte benötigen, um Werte zurückzugeben:
function foo(n) {
return function (i) {
return n += i } }
(Um fair zu sein, behält Perl ebenfalls diese Unterscheidung bei, geht aber auf typische Perl-Art damit um, indem es Ihnen erlaubt, Rückgaben 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 halten. Und obwohl Python einen Funktionstyp hat, gibt es keine literale Darstellung dafür (es sei denn, der Körper ist nur ein einzelner Ausdruck), sodass Sie eine benannte Funktion erstellen müssen, die zurückgegeben wird. Das ist, was Sie am Ende haben:
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 ich vermute, dass sie das wahrscheinlich eines Tages tun werden. (Aber wenn sie nicht auf die Evolution von Python warten wollen, um den Rest des Weges nach Lisp zu gelangen, könnten sie immer einfach...)
In OO-Sprachen können Sie, in begrenztem Umfang, eine Closure (eine Funktion, die auf Variablen verweist, die in umschließenden Bereichen definiert sind) simulieren, indem Sie eine Klasse mit einer Methode und einem Feld definieren, um jede Variable aus einem umschließenden Bereich zu ersetzen. Dies zwingt den Programmierer dazu, die Art von Codeanalyse durchzuführen, die der Compiler in einer Sprache mit vollständiger Unterstützung für lexikalische Bereiche 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 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 füge dies hinzu, weil ich nicht möchte, dass die Befürworter von Python 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 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 bisschen ein Hack zu sein.
Im Wettstreit 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 nicht klar, ob Sie dieses Problem tatsächlich lösen können. Ken Anderson sagt, dass der folgende Code so nah ist, wie Sie in Java kommen können:
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;
}};
}
Das entspricht nicht der Spezifikation, 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 vorhergehenden Beispiele verhält, irgendwo zwischen verdammt umständlich und unmöglich ist. Wenn jemand einen schreiben möchte, wäre ich sehr neugierig, ihn zu sehen, aber ich persönlich habe die Geduld verloren.
Es ist natürlich nicht wörtlich wahr, dass Sie dieses Problem in anderen Sprachen nicht lösen können. Die Tatsache, dass all diese Sprachen Turing-äquivalent sind, bedeutet, dass Sie streng genommen jedes Programm in jeder von ihnen schreiben können. Wie würden Sie es also tun? Im Grenzfall, indem Sie einen Lisp-Interpreter in der weniger leistungsfähigen Sprache schreiben.
Das klingt wie ein Scherz, aber es passiert so oft in unterschiedlichem Maße in großen Programmierprojekten, dass es einen Namen für das Phänomen gibt, Greenspun's Zehntes Gesetz:
Jedes ausreichend komplizierte C- oder Fortran-Programm enthält eine ad-hoc, informell spezifizierte, fehlerbehaftete, langsame Implementierung von der Hälfte von Common Lisp.
Wenn Sie versuchen, ein schwieriges Problem zu lösen, ist die Frage nicht, ob Sie eine leistungsfähige Sprache verwenden werden, sondern ob Sie (a) eine leistungsfähige 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, wo wir in der Tat den Code simulieren, den ein Compiler generieren würde, um eine lexikalische Variable zu implementieren.
Diese Praxis ist nicht nur verbreitet, sondern auch institutionalisiert. Zum Beispiel hören Sie in der OO-Welt viel über "Muster". Ich frage mich, ob diese Muster nicht manchmal Beweise für Fall (c), den menschlichen Compiler, sind. Wenn ich Muster in meinen Programmen sehe, betrachte ich das als ein 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, dass ich Abstraktionen verwende, die nicht leistungsfähig genug sind – oft, dass ich von Hand die Erweiterungen eines Makros generiere, das ich schreiben muss.
Anmerkungen
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 Sie einen spitzhaarigen Chef dazu bringen möchten, Ihnen zu erlauben, Software in Lisp zu schreiben, könnten Sie 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 "Best Practices der Branche" bei JPL inspirierte mich, diesen allgemein missbrauchten Ausdruck anzusprechen.
Peter Norvig stellte fest, dass 16 der 23 Muster in Design Patterns "unsichtbar oder einfacher" in Lisp waren.
Danke an die vielen Menschen, 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 an den geäußerten Meinungen.
Verwandt:
Viele Menschen haben auf diesen Vortrag reagiert, daher habe ich eine zusätzliche Seite eingerichtet, um die Themen zu behandeln, die sie angesprochen haben: Re: Rache der Nerds.
Es hat auch eine umfangreiche und oft nützliche Diskussion in der LL1 Mailingliste ausgelöst. Besonders zu beachten ist die Mail von Anton van Straaten über semantische Kompression.
Einige der Mails in LL1 führten mich dazu, tiefer in das Thema Sprachleistung in Kürze ist Macht einzutauchen.
Eine größere Sammlung kanonischer Implementierungen des Akkumulator-Generator-Benchmarks ist auf ihrer eigenen Seite zusammengetragen.
Japanische Übersetzung, Spanische Übersetzung, Chinesische Übersetzung