DIE RACHE DER EIERKÖPFE
OriginalMai 2002
„Wir waren hinter den C++-Programmierern her. Es ist uns gelungen, viele von ihnen bis zur Hälfte des Weges zu Lisp zu schleppen.“
- Guy Steele, Co-Autor der Java-Spezifikation
In der Softwarebranche herrscht ein andauernder Kampf zwischen den spitzköpfigen Akademikern und einer anderen, ebenso gewaltigen Macht, den spitzhaarigen Chefs. Jeder weiß, wer der spitzhaarige Chef ist, oder? Ich denke, die meisten Leute in der Technologiewelt erkennen nicht nur diese Zeichentrickfigur, sondern kennen auch die tatsächliche Person in ihrem Unternehmen, die ihm nachempfunden ist.
Der Chef mit den spitzen Haaren vereint auf wundersame Weise zwei Eigenschaften, die für sich genommen zwar häufig vorkommen, aber selten gemeinsam auftreten: (a) er hat überhaupt keine Ahnung von Technologie und (b) er hat eine sehr starke Meinung dazu.
Nehmen wir zum Beispiel an, Sie müssen eine Software schreiben. Der spitzhaarige Chef hat keine Ahnung, wie diese Software funktionieren soll, und kann Programmiersprachen nicht voneinander unterscheiden. Trotzdem weiß er, in welcher Sprache Sie die Software schreiben sollen. Genau. Er meint, Sie sollten sie in Java schreiben.
Warum denkt er das? Werfen wir einen Blick in das Gehirn des spitzhaarigen Chefs. Was er denkt, ist ungefähr Folgendes: Java ist ein Standard. Ich weiß, dass es einer sein muss, denn ich lese ständig in der Presse darüber. 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 also die Programmierer, die jetzt für mich arbeiten, kündigen, was auf mysteriöse Weise immer passiert, kann ich sie leicht ersetzen.
Nun, das klingt nicht so abwegig. 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 den Nagel auf den Kopf getroffen. Wenn alle Sprachen gleichwertig sind, dann kann man natürlich die Sprache verwenden, die alle anderen verwenden.
Aber nicht alle Sprachen sind gleichwertig, und ich glaube, ich kann Ihnen das beweisen, ohne auf die Unterschiede zwischen ihnen eingehen zu müssen. Wenn Sie den spitzhaarigen Chef 1992 gefragt hätten, in welcher Sprache Software geschrieben werden sollte, hätte er genauso ohne Zögern geantwortet wie heute. Software sollte in C++ geschrieben werden. Aber wenn alle Sprachen gleichwertig sind, warum sollte sich die Meinung des spitzhaarigen Chefs dann jemals ändern? Warum hätten sich die Entwickler von Java überhaupt die Mühe machen sollen, eine neue Sprache zu entwickeln?
Wenn Sie eine neue Sprache entwickeln, tun Sie das vermutlich, weil Sie denken, sie sei in irgendeiner Weise besser 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 also: Sprachen sind nicht alle gleichwertig. Wenn Sie der Spur durch das Gehirn des spitzhaarigen Chefs zu Java und dann zurück durch die Geschichte von Java bis zu seinen Ursprüngen folgen, haben Sie am Ende eine Idee, die Ihrer Ausgangsannahme widerspricht.
Wer hat also Recht? James Gosling oder der spitzhaarige Chef? Wenig überraschend hat Gosling Recht. Manche Sprachen sind für bestimmte Probleme besser als andere. Und das wirft interessante Fragen auf. Java wurde so konzipiert, dass es für bestimmte Probleme besser ist als C++. Für welche Probleme? Wann ist Java besser und wann C++? Gibt es Situationen, in denen andere Sprachen besser sind als die eine oder andere?
Wenn Sie anfangen, über diese Frage nachzudenken, haben Sie eine Büchse der Pandora geöffnet. Wenn der spitzhaarige Chef über das Problem in seiner ganzen Komplexität nachdenken müsste, würde ihm das Gehirn platzen. Solange er alle Sprachen als gleichwertig betrachtet, muss er nur diejenige auswählen, die am meisten Schwung zu haben scheint, und da dies eher eine Frage der Mode als der Technologie ist, kann wahrscheinlich sogar er die richtige Antwort finden. Aber wenn die Sprachen unterschiedlich sind, muss er plötzlich zwei Gleichungen gleichzeitig lösen und versuchen, ein optimales Gleichgewicht 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 Chancen, für jede davon Programmierer, Bibliotheken usw. zu finden. Wenn das auf der anderen Seite der Tür liegt, ist es keine Überraschung, dass der spitzhaarige Chef sie nicht öffnen möchte.
Der Nachteil der Annahme, dass alle Programmiersprachen gleichwertig sind, ist, dass dies nicht stimmt. Der Vorteil ist jedoch, dass es Ihr Leben viel einfacher macht. Und ich denke, das ist der Hauptgrund, warum diese Vorstellung so weit verbreitet ist. Es ist eine bequeme Vorstellung.
Wir wissen, dass Java ziemlich gut sein muss, weil es die coole, neue Programmiersprache ist. Oder ist es das? Wenn man die Welt der Programmiersprachen aus der Ferne betrachtet, sieht es so aus, als wäre Java der letzte Schrei. (Von weit genug entfernt sieht man nur die große, blinkende Werbetafel, die von Sun bezahlt wurde.) Aber wenn man sich diese Welt aus der Nähe ansieht, stellt man fest, dass es verschiedene Abstufungen von Coolness gibt. Innerhalb der Hacker-Subkultur gibt es eine andere Sprache namens Perl, die als viel cooler als Java gilt. Slashdot zum Beispiel wird von Perl generiert. Ich glaube nicht, dass man diese Leute mit Java Server Pages antreffen würde. Aber es gibt eine andere, neuere Sprache namens Python, deren Benutzer dazu neigen, auf Perl herabzublicken, und weitere, die in den Startlöchern stehen.
Wenn Sie sich diese Sprachen der Reihe nach ansehen (Java, Perl, Python), bemerken Sie ein interessantes Muster. Zumindest fällt Ihnen dieses Muster auf, 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. Wir schreiben das Jahr 2002 und die Programmiersprachen haben fast den Stand von 1958 erreicht.
Mathe nachholen
Ich meine damit, dass Lisp erstmals 1958 von John McCarthy entdeckt wurde und dass populäre Programmiersprachen erst jetzt mit den Ideen, die er damals entwickelte, Schritt halten.
Wie kann das nun wahr sein? Ist die Computertechnologie nicht etwas, das sich sehr schnell verändert? Ich meine, 1958 waren Computer noch Ungetüme in der Größe eines Kühlschranks mit der Rechenleistung einer Armbanduhr. Wie konnte eine so alte Technologie überhaupt relevant sein, geschweige denn den neuesten Entwicklungen überlegen?
Ich sage Ihnen, wie. Lisp wurde nicht wirklich als Programmiersprache konzipiert, zumindest nicht in dem Sinne, wie wir es heute verstehen. Was wir unter einer Programmiersprache verstehen, ist etwas, mit dem wir einem Computer sagen, was er tun soll. McCarthy hatte letztendlich tatsächlich die Absicht, eine Programmiersprache in diesem Sinne zu entwickeln, aber das Lisp, das wir letztendlich entwickelt haben, basierte auf etwas anderem, das er als theoretische Übung entwickelt hatte – dem Versuch, eine praktischere Alternative zur Turingmaschine zu definieren. Wie McCarthy später sagte:
Eine andere Möglichkeit, zu zeigen, dass Lisp übersichtlicher ist als Turingmaschinen, bestand darin, eine universelle Lisp-Funktion zu schreiben und zu zeigen, dass sie kürzer und verständlicher ist als die Beschreibung einer universellen Turingmaschine. 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 bedenken, dass sie in der Praxis zum Ausdrücken von Lisp-Programmen verwendet werden würde.
Was dann geschah, war, dass irgendwann Ende 1958 Steve Russell, einer von McCarthys Doktoranden, sich diese Definition von „eval“ ansah und erkannte, dass das Ergebnis ein Lisp-Interpreter wäre, wenn er sie in Maschinensprache übersetzen würde.
Das war damals eine große Überraschung. Hier ist, was McCarthy später in einem Interview dazu sagte:
Steve Russell sagte: „Schau mal, warum programmiere ich nicht diese Evaluierung ...“, und ich sagte zu ihm: „Ho, ho, du verwechselst Theorie mit Praxis, diese Evaluierung ist zum Lesen gedacht, nicht zum Rechnen.“ Aber er machte es einfach. Das heißt, er kompilierte die Evaluierung in meinem Artikel in [IBM] 704-Maschinencode, behob Fehler und bewarb dies dann als Lisp-Interpreter, was es zweifellos war. Zu diesem Zeitpunkt hatte Lisp also im Wesentlichen die Form, die es heute hat ...
Plötzlich, ich glaube innerhalb weniger Wochen, hatte sich McCarthys theoretische Übung in eine echte Programmiersprache verwandelt – und zwar in eine leistungsfähigere, als er beabsichtigt hatte.
Die kurze Erklärung, warum diese Sprache aus den 1950er Jahren nicht veraltet ist, lautet also, dass es sich nicht um Technologie, sondern um Mathematik handelte, und Mathematik veraltet nicht. Der richtige Vergleichspunkt für Lisp ist nicht die Hardware aus den 1950er Jahren, sondern beispielsweise der Quicksort-Algorithmus, der 1960 entdeckt wurde und noch immer die schnellste Allzwecksortierung ist.
Es gibt noch eine andere Sprache aus den 1950er Jahren, Fortran, die den entgegengesetzten Ansatz zur Sprachentwicklung darstellt. Lisp war ein Stück Theorie, das unerwartet in eine Programmiersprache umgewandelt wurde. Fortran wurde absichtlich als Programmiersprache entwickelt, aber als eine, die wir heute als sehr niedrigstufige Sprache bezeichnen würden.
Fortran I , die Sprache, die 1956 entwickelt wurde, war ein ganz anderes Kaliber als das heutige Fortran. Fortran I war im Wesentlichen eine Assemblersprache mit Mathematik. In mancher Hinsicht war es weniger leistungsfähig als neuere Assemblersprachen; es gab beispielsweise keine Unterprogramme, sondern nur Verzweigungen. Das heutige Fortran ist wohl näher an Lisp als an Fortran I.
Lisp und Fortran waren die Stämme zweier verschiedener Evolutionsbäume, von denen einer in der Mathematik und der andere in der Maschinenarchitektur verwurzelt war. Diese beiden Bäume nähern sich seither immer mehr einander an. Lisp begann leistungsfähig und wurde in den nächsten zwanzig Jahren immer schneller. Die so genannten Mainstream-Sprachen begannen leistungsfähiger und wurden in den nächsten vierzig Jahren immer leistungsfähiger, bis die fortschrittlichsten von ihnen heute recht nah an Lisp heranreichen. Nah dran, aber ihnen fehlen noch immer ein paar Dinge ...
Was Lisp anders machte
Als Lisp zum ersten Mal entwickelt wurde, verkörperte es neun neue Ideen. Einige davon halten wir heute für selbstverständlich, andere sind nur in fortgeschritteneren Sprachen zu finden und zwei sind noch immer einzigartig in Lisp. Die neun Ideen sind in der Reihenfolge ihrer Übernahme durch den Mainstream:
Konditionale. Ein Konditional ist eine Wenn-Dann-Sonst-Konstruktion. Heute halten wir diese für selbstverständlich, aber Fortran I hatte sie nicht. Es hatte nur ein Konditional, das eng auf dem zugrunde liegenden Maschinenbefehl basierte.
Ein Funktionstyp. In Lisp sind Funktionen ein Datentyp, genau wie Ganzzahlen oder Zeichenfolgen. Sie haben eine wörtliche Darstellung, können in Variablen gespeichert werden, können als Argumente übergeben werden und so weiter.
Rekursion. Lisp war die erste Programmiersprache, die dies unterstützte.
Dynamische Typisierung. In Lisp sind alle Variablen tatsächlich Zeiger. Werte haben Typen, nicht Variablen, und das Zuweisen oder Binden von Variablen bedeutet das Kopieren von Zeigern, nicht das, worauf sie zeigen.
Müllabfuhr.
Aus Ausdrücken bestehende Programme. Lisp-Programme sind Ausdrucksbäume, 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 ganz natürlich, da man keine Anweisungen verschachteln konnte. Und obwohl man Ausdrücke brauchte, damit die Mathematik funktionierte, hatte es keinen Sinn, irgendetwas anderes einen Wert zurückgeben zu lassen, da nichts darauf warten konnte.
Diese Einschränkung verschwand mit dem Aufkommen blockstrukturierter Sprachen, aber da war es schon zu spät. Die Unterscheidung zwischen Ausdrücken und Anweisungen war fest verankert. Sie breitete sich von Fortran auf Algol und dann auf beide Nachkommen aus.
Ein Symboltyp. Symbole sind tatsächlich Zeiger auf Zeichenfolgen, die in einer Hash-Tabelle gespeichert sind. Sie können also die Gleichheit testen, indem Sie einen Zeiger vergleichen, anstatt jedes einzelne Zeichen zu vergleichen.
Eine Notation für Code unter Verwendung von Symbol- und Konstantenbäumen.
Die ganze Sprache ist immer da. Es gibt keinen wirklichen Unterschied zwischen Lese-, Kompilier- und Laufzeit. Sie können Code beim Lesen kompilieren oder ausführen, Code beim Kompilieren lesen oder ausführen und Code zur Laufzeit lesen oder kompilieren.
Durch Ausführen von Code zur Lesedauer können Benutzer die Syntax von Lisp neu programmieren. Das Ausführen von Code zur Kompilierzeit bildet die Grundlage für Makros. Das Kompilieren zur Laufzeit bildet die Grundlage für die Verwendung von Lisp als Erweiterungssprache in Programmen wie Emacs. Und das Lesen zur Laufzeit ermöglicht Programmen die Kommunikation mithilfe von S-Ausdrücken, einer Idee, die vor Kurzem als XML neu erfunden wurde.
Als Lisp zum ersten Mal auftauchte, waren diese Ideen weit entfernt von der normalen Programmierpraxis, die weitgehend von der in den späten 1950er Jahren verfügbaren Hardware bestimmt wurde. Im Laufe der Zeit entwickelte sich die Standardsprache, die in einer Reihe populärer Sprachen enthalten ist, allmählich in Richtung Lisp. Die Ideen 1 bis 5 sind mittlerweile 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 ist dies vielleicht die interessanteste von allen. Die Ideen 8 und 9 wurden nur durch Zufall Teil von Lisp, weil Steve Russell etwas implementierte, was McCarthy nie beabsichtigt hatte. Und doch erweisen sich diese Ideen als verantwortlich sowohl für Lisps seltsames Erscheinungsbild als auch für seine markantesten Merkmale. Lisp sieht nicht so sehr seltsam aus, weil es eine seltsame Syntax hat, sondern weil es keine Syntax hat; Sie drücken Programme direkt in den Parsebäumen aus, die im Hintergrund erstellt werden, wenn andere Sprachen geparst werden, und diese Bäume bestehen aus Listen, die Lisp-Datenstrukturen sind.
Das Ausdrücken der Sprache in eigenen Datenstrukturen erweist sich als sehr mächtiges Feature. Die Ideen 8 und 9 zusammen bedeuten, dass Sie Programme schreiben können, die Programme schreiben. Das mag wie eine bizarre Idee klingen, ist aber in Lisp eine alltägliche Sache. Die gebräuchlichste Methode hierfür ist etwas, das man Makro nennt.
Der Begriff „Makro“ bedeutet in Lisp nicht das Gleiche wie in anderen Sprachen. Ein Lisp-Makro kann alles Mögliche 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 Ihnen mehr über Makros erzählen .
Makros (im Sinne von Lisp) sind, soweit ich weiß, immer noch Lisp vorbehalten. Das liegt zum Teil daran, dass man, um Makros zu haben, seine Sprache wahrscheinlich so seltsam aussehen lassen muss wie Lisp. Es kann auch daran liegen, dass man, wenn man diese letzte Leistungssteigerung hinzufügt, 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 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 gesamten Rest von Lisp daraus erstellen. Das ist in der Tat die entscheidende Eigenschaft von Lisp: Um dies zu erreichen, hat McCarthy Lisp die Form gegeben, die es hat.
Wo Sprachen wichtig sind
Angenommen, Lisp stellt eine Art Grenze dar, der sich die gängigen Sprachen asymptotisch nähern – heißt das dann, 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 an der Spitze der Innovation zu stehen? Und ist Popularität nicht bis zu einem gewissen Grad ihre eigene Rechtfertigung? Hat der spitzhaarige Chef beispielsweise nicht recht, wenn er eine Sprache verwenden möchte, für die er problemlos Programmierer einstellen kann?
Es gibt natürlich Projekte, bei denen die Wahl der Programmiersprache keine große Rolle spielt. In der Regel gilt: Je anspruchsvoller die Anwendung, desto mehr Vorteile bietet Ihnen die Verwendung einer leistungsstarken Sprache. Viele Projekte sind jedoch überhaupt nicht anspruchsvoll. Der Großteil der Programmierung besteht wahrscheinlich aus dem Schreiben kleiner Verbindungsprogramme, und für kleine Verbindungsprogramme können Sie jede Sprache verwenden, mit der Sie bereits vertraut sind und die gute Bibliotheken für das bietet, was Sie tun müssen. Wenn Sie nur Daten von einer Windows-Anwendung in eine andere übertragen müssen, verwenden Sie natürlich Visual Basic.
Sie können auch in Lisp kleine Verbindungsprogramme schreiben (ich verwende es als Desktop-Rechner), aber der größte Vorteil für Sprachen wie Lisp liegt am anderen Ende des Spektrums, wo Sie anspruchsvolle Programme schreiben müssen, um schwierige Probleme angesichts der starken Konkurrenz zu lösen. Ein gutes Beispiel ist das Flugpreissuchprogramm , das ITA Software an Orbitz lizenziert. Diese Jungs sind in einen Markt eingestiegen, der bereits von zwei großen, etablierten Konkurrenten, Travelocity und Expedia, dominiert wird, und scheinen diese technologisch gerade gedemütigt zu haben.
Der Kern der Anwendung von ITA ist ein 200.000 Zeilen umfassendes Common Lisp-Programm, das um ein Vielfaches mehr Möglichkeiten durchsucht als die Konkurrenz, die offenbar noch Programmiertechniken aus der Mainframe-Ära verwendet. (Obwohl ITA in gewisser Weise auch eine Programmiersprache aus der Mainframe-Ära verwendet.) Ich habe noch nie etwas von ITAs Code gesehen, aber einem ihrer Top-Hacker zufolge verwenden sie viele Makros, und das überrascht mich nicht.
Zentripetalkräfte
Ich sage nicht, dass der Einsatz ungewöhnlicher Technologien kostenlos ist. Der spitzhaarige Chef liegt nicht ganz falsch, 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 durch die Verwendung weniger gebräuchlicher Sprachen entstehen können. 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 Probleme, Programmierer einzustellen.
Wie problematisch ist jeder dieser Punkte? Die Wichtigkeit des ersten hängt davon ab, ob Sie die Kontrolle über das gesamte System haben. Wenn Sie Software schreiben, die auf dem Computer eines Remote-Benutzers auf einem fehlerhaften, geschlossenen Betriebssystem (ich nenne keine Namen) ausgeführt werden muss, kann es von Vorteil sein, Ihre Anwendung in derselben Sprache wie das Betriebssystem zu schreiben. Wenn Sie jedoch das gesamte System kontrollieren und den Quellcode aller Teile haben, wie dies bei ITA vermutlich der Fall ist, können Sie jede beliebige Sprache verwenden. Wenn Inkompatibilitäten auftreten, können Sie diese selbst beheben.
In serverbasierten Anwendungen kann man die modernsten Technologien verwenden, und ich glaube, das ist der Hauptgrund 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-Apps verwenden, sondern weil sie auf Servern verwendet werden. Und da Software vom Desktop auf Server verlagert wird (eine Zukunft, mit der sich selbst Microsoft abgefunden zu haben scheint), wird der Druck, Mittelweg-Technologien zu verwenden, immer geringer.
Was Bibliotheken betrifft, hängt ihre Bedeutung auch von der Anwendung ab. Bei weniger anspruchsvollen Problemen kann die Verfügbarkeit von Bibliotheken die intrinsische Leistungsfähigkeit der Sprache überwiegen. Wo liegt der Break-Even-Punkt? Schwer zu sagen, aber wo immer er liegt, liegt er unter allem, was man wahrscheinlich als Anwendung bezeichnen würde. Wenn sich ein Unternehmen als im Softwaregeschäft tätig betrachtet und eine Anwendung schreibt, die eines seiner Produkte sein wird, dann werden wahrscheinlich mehrere Hacker daran beteiligt sein und das Schreiben wird mindestens sechs Monate dauern. Bei einem Projekt dieser Größenordnung überwiegen leistungsfähige Sprachen wahrscheinlich den Komfort bereits vorhandener Bibliotheken.
Die dritte Sorge des spitzhaarigen Chefs, die Schwierigkeit, Programmierer einzustellen, ist meiner Meinung nach ein Ablenkungsmanöver. Wie viele Hacker müssen Sie schließlich einstellen? Wir alle wissen mittlerweile, dass Software am besten von Teams mit weniger als zehn Leuten entwickelt wird. Und Sie sollten keine Probleme haben, Hacker in dieser Größenordnung für jede Sprache einzustellen, von der irgendjemand je gehört hat. Wenn Sie keine zehn Lisp-Hacker finden können, ist Ihr Unternehmen wahrscheinlich in der falschen Stadt für die Softwareentwicklung angesiedelt.
Tatsächlich verringert die Wahl einer leistungsfähigeren Sprache wahrscheinlich die Größe des benötigten Teams, weil (a) Sie bei Verwendung einer leistungsfähigeren Sprache wahrscheinlich nicht so viele Hacker benötigen und (b) Hacker, die mit fortgeschritteneren Sprachen arbeiten, wahrscheinlich intelligenter sind.
Ich sage nicht, dass Sie nicht stark unter Druck geraten werden, vermeintlich „Standard“-Technologien zu verwenden. Bei Viaweb (jetzt Yahoo Store) sorgten wir bei Risikokapitalgebern und potenziellen Käufern für Aufsehen, weil wir Lisp verwendeten. Aber wir sorgten auch für Aufsehen, weil wir als Server generische Intel-Boxen anstelle von „industrietauglichen“ Servern wie Suns verwendeten, weil wir eine damals noch obskure Open-Source-Unix-Variante namens FreeBSD anstelle eines echten kommerziellen Betriebssystems wie Windows NT verwendeten, weil wir einen angeblichen E-Commerce-Standard namens SET ignorierten, 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 verwendeten? Einige ein wenig, aber wenn wir Lisp nicht verwendet hätten, hätten wir nicht die Software schreiben können, die sie dazu veranlasste, uns zu kaufen. Was ihnen wie eine Anomalie vorkam, war in Wirklichkeit 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 Sie das nicht tun, wird es niemanden interessieren, wie angenehm orthodox Ihre Technologieauswahl war.
Der Preis des Mittelmaßes
Wie viel verlieren Sie, wenn Sie eine weniger leistungsfähige Sprache verwenden? Darüber gibt es tatsächlich einige Daten.
Das bequemste Maß für Leistung ist wahrscheinlich die Codegröße . Der Sinn von Hochsprachen besteht darin, Ihnen größere Abstraktionen zu bieten – sozusagen größere Bausteine, sodass Sie nicht so viele benötigen, um eine Mauer einer bestimmten Größe zu bauen. Je leistungsfähiger die Sprache ist, desto kürzer ist das Programm (natürlich nicht nur in Zeichen, sondern in einzelnen Elementen).
Wie können Sie mit einer leistungsfähigeren Sprache kürzere Programme schreiben? Eine Technik, die Sie verwenden können, wenn die Sprache es zulässt, ist die sogenannte Bottom-up-Programmierung . Anstatt Ihre Anwendung einfach in der Basissprache zu schreiben, bauen Sie auf der Basissprache eine Sprache zum Schreiben von Programmen wie Ihrem 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 leichter zu ändern sein, da in vielen Fällen die Sprachebene überhaupt nicht geändert werden muss.
Die Codegröß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 das Schreiben auch dreimal so lange dauern – und Sie können dies nicht dadurch umgehen, dass Sie mehr Leute einstellen, denn ab einer bestimmten Größe sind Neueinstellungen tatsächlich ein Nettoverlust. Fred Brooks hat dieses Phänomen in seinem berühmten Buch The Mythical Man-Month beschrieben, und alles, was ich gesehen habe, hat seine Aussage eher bestätigt.
Wie viel kürzer sind Ihre Programme also, wenn Sie sie in Lisp schreiben? Die meisten Zahlen, die ich beispielsweise für Lisp im Vergleich zu C gehört habe, lagen bei etwa 7-10x. Aber in einem kürzlich erschienenen Artikel über ITA im Magazin New Architect hieß es, dass „eine Zeile Lisp 20 Zeilen C ersetzen kann“, und da dieser Artikel voller Zitate des ITA-Präsidenten war, gehe ich davon aus, dass sie diese Zahl von der ITA haben. Wenn das so ist, können wir darauf vertrauen; die Software der ITA enthält viel C und C++ sowie Lisp, sie sprechen also aus Erfahrung.
Ich vermute, dass diese Vielfachen nicht einmal konstant sind. Ich denke, sie steigen, wenn man vor schwierigeren Problemen steht und auch, wenn man intelligentere Programmierer hat. Ein wirklich guter Hacker kann aus besseren Tools mehr herausholen.
Als ein Datenpunkt auf der Kurve sei erwähnt, dass ITA, wenn Sie mit ihm konkurrieren und Ihre Software in C schreiben würden, zwanzigmal schneller Software entwickeln könnte 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 dagegen nur drei Monate an der Entwicklung einer neuen Funktion arbeiten würden, würde es fünf Jahre dauern, bis Sie diese auch hätten.
Und wissen Sie was? Das ist das beste Szenario. Wenn Sie über Codegrößenverhältnisse sprechen, gehen Sie implizit davon aus, dass Sie das Programm tatsächlich in der schwächeren Sprache schreiben können. Tatsächlich sind den Möglichkeiten der Programmierer jedoch Grenzen gesetzt. Wenn Sie versuchen, ein schwieriges Problem mit einer Sprache zu lösen, die zu niedrig ist, gelangen Sie an einen Punkt, an dem Sie einfach zu viel auf einmal im Kopf behalten müssen.
Wenn ich also sage, dass der imaginäre Konkurrent von ITA fünf Jahre brauchen würde, um etwas zu kopieren, das ITA in drei Monaten in Lisp schreiben könnte, dann meine ich fünf Jahre, wenn nichts schiefgeht. Tatsächlich wird ein Entwicklungsprojekt, das fünf Jahre dauert, wahrscheinlich nie fertiggestellt, so wie es in den meisten Unternehmen läuft.
Ich gebe zu, dass dies ein Extremfall ist. Die Hacker von ITA scheinen ungewöhnlich schlau zu sein, und C ist eine ziemlich einfache Sprache. Aber in einem wettbewerbsorientierten Markt würde sogar ein Unterschied von zwei oder drei zu eins ausreichen, um zu garantieren, dass Sie immer im Rückstand wären.
Ein Rezept
Dies ist die Art von Möglichkeit, an die der spitzhaarige Chef nicht einmal denken möchte. Und deshalb tun es die meisten von ihnen auch nicht. Denn, wissen Sie, wenn es darauf ankommt, macht es dem spitzhaarigen Chef nichts aus, wenn seine Firma in den Hintern getreten wird, solange niemand beweisen kann, dass es seine Schuld ist. Der sicherste Plan für ihn persönlich ist, sich in der Mitte der Herde zu halten.
In großen Unternehmen wird dieser Ansatz mit dem Schlagwort „Best Practice der Branche“ beschrieben. Damit soll der spitzhaarige Chef von der Verantwortung befreit werden: Wenn er sich für etwas entscheidet, das „Best Practice der Branche“ ist, und das Unternehmen verliert, kann man ihm dafür keine Schuld geben. Nicht er hat sich entschieden, sondern die Branche.
Ich glaube, dieser Begriff wurde ursprünglich verwendet, um Buchhaltungsmethoden und so weiter zu beschreiben. Er bedeutet ungefähr, nichts Ungewöhnliches zu tun. Und in der Buchhaltung ist das wahrscheinlich eine gute Idee. Die Begriffe „topaktuell“ und „Buchhaltung“ passen nicht gut zusammen. Aber wenn man dieses Kriterium in Entscheidungen über Technologie einbezieht, bekommt man die falschen Antworten.
Technologie sollte oft auf dem neuesten Stand sein. Wie Erann Gat betont hat, erreicht man bei Programmiersprachen mit „Best Practice der Branche“ nicht das Beste, sondern nur den Durchschnitt. Wenn eine Entscheidung dazu führt, dass Sie Software mit einem Bruchteil der Geschwindigkeit aggressiverer Konkurrenten entwickeln, ist „Best Practice“ eine falsche Bezeichnung.
Hier haben wir also zwei Informationen, die ich für sehr wertvoll halte. Tatsächlich weiß ich das aus eigener Erfahrung. Erstens: Sprachen sind unterschiedlich leistungsfähig. Zweitens: Die meisten Manager ignorieren dies bewusst. Zusammengenommen sind diese beiden Tatsachen buchstäblich ein Rezept zum Geldverdienen. ITA ist ein Beispiel für dieses Rezept in Aktion. Wenn Sie im Softwaregeschäft erfolgreich sein wollen, nehmen Sie sich einfach das schwierigste Problem vor, das Sie finden können, verwenden Sie die leistungsfähigste Sprache, die Sie bekommen können, und warten Sie, bis die spitzhaarigen Chefs Ihrer Konkurrenten wieder zum Mittelmaß zurückkehren.
Anhang: Leistung
Um zu verdeutlichen, was ich mit der relativen Leistungsfähigkeit von Programmiersprachen meine, betrachten wir das folgende Problem. Wir möchten eine Funktion schreiben, die Akkumulatoren erzeugt - eine Funktion, die eine Zahl n annimmt und eine Funktion zurückgibt, die eine andere Zahl i annimmt und n erhöht um i zurückgibt.
(Das wird um erhöht , nicht um plus. Ein Akkumulator muss akkumulieren.)
In Common Lisp wäre dies
(defun foo (n) (lambda (i) (incf ni)))
und in Perl 5,
sub foo { my ($n) = @_; sub {$n += shift} }
das mehr Elemente als die Lisp-Version hat, weil Sie Parameter in Perl manuell extrahieren müssen.
In Smalltalk ist der Code etwas länger als in Lisp
foo: n |s| s := n. ^[:i| s := s+i. ]
denn obwohl lexikalische Variablen im Allgemeinen funktionieren, können Sie keine Zuweisung zu einem Parameter vornehmen, sodass Sie eine neue Variable s erstellen müssen.
In Javascript ist das Beispiel wiederum etwas länger, da Javascript die Unterscheidung zwischen Anweisungen und Ausdrücken beibehält und Sie daher explizite return-Anweisungen benötigen, um Werte zurückzugeben:
function foo(n) { return function (i) { return n += i } }
(Fairerweise muss man sagen, dass Perl diese Unterscheidung ebenfalls beibehält, aber in typischer Perl-Manier damit umgeht, indem es Ihnen 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, die den Wert von n enthält. Und obwohl Python einen Funktionsdatentyp hat, gibt es keine wörtliche Darstellung dafür (es sei denn, der Hauptteil besteht nur aus einem einzelnen Ausdruck), sodass Sie eine benannte Funktion erstellen müssen, die zurückgegeben wird. Das ist das Ergebnis:
def foo(n): s = [n] def bar(i): s[0] += i return s[0] return bar
Python-Benutzer fragen sich vielleicht zu Recht, warum sie nicht einfach schreiben können
def foo(n): return lambda i: return n += i
oder auch
def foo(n): lambda i: n += i
und ich schätze, dass sie das eines Tages wahrscheinlich tun werden. (Aber wenn sie nicht warten wollen, bis Python sich zu Lisp weiterentwickelt hat, können sie immer noch einfach...)
In OO-Sprachen können Sie in begrenztem Umfang einen Abschluss (eine Funktion, die auf Variablen verweist, die in umschließenden Gültigkeitsbereichen definiert sind) simulieren, indem Sie eine Klasse mit einer Methode und einem Feld definieren, um jede Variable aus einem umschließenden Gültigkeitsbereich zu ersetzen. Dadurch muss der Programmierer die Art von Codeanalyse durchführen, die der Compiler in einer Sprache mit voller Unterstützung für lexikalische Gültigkeitsbereiche durchführen würde. Dies funktioniert nicht, wenn mehr als eine Funktion auf dieselbe Variable verweist, reicht jedoch 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 man 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, ich würde die Sprache falsch darstellen, aber beide scheinen mir komplexer als die erste Version. Sie tun dasselbe, indem Sie einen separaten Ort einrichten, um den Akkumulator zu speichern; es ist nur ein Feld in einem Objekt statt dem Kopf einer Liste. Und die Verwendung dieser speziellen, reservierten Feldnamen, insbesondere call , scheint ein bisschen ein Hack zu sein.
In der Rivalität zwischen Perl und Python behaupten die Python-Hacker anscheinend, dass Python eine elegantere Alternative zu Perl sei. Dieser Fall zeigt jedoch, dass die höchste Eleganz in der Leistung liegt: 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 in Java so nahe kommt, wie es nur geht:
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 entspricht nicht der Spezifikation, da 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 liegt. Wenn jemand eine schreiben möchte, wäre ich sehr neugierig, sie zu sehen, aber mir persönlich ist die Zeit abgelaufen.
Es ist natürlich nicht wörtlich wahr, dass Sie dieses Problem nicht in anderen Sprachen lösen können. Die Tatsache, dass alle diese Sprachen Turing-äquivalent sind, bedeutet, dass Sie streng genommen jedes Programm in jeder dieser Sprachen schreiben können. Wie würden Sie das also tun? Im Grenzfall, indem Sie einen Lisp-Interpreter in der weniger leistungsfähigen Sprache schreiben.
Das klingt wie ein Witz, kommt aber in großen Programmierprojekten so häufig und in unterschiedlichem Ausmaß vor, dass es für dieses Phänomen einen Namen gibt: Greenspuns zehnte Regel:
Jedes ausreichend komplizierte C- oder Fortran-Programm enthält eine ad hoc informell spezifizierte, fehlerbehaftete, langsame Implementierung der Hälfte von Common Lisp.
Wenn Sie versuchen, ein schwieriges Problem zu lösen, ist die Frage nicht, ob Sie eine ausreichend leistungsstarke Sprache verwenden, sondern ob Sie (a) eine leistungsstarke Sprache verwenden, (b) einen De-facto-Interpreter für eine solche Sprache schreiben oder (c) selbst zum menschlichen Compiler für eine solche Sprache werden. Wir sehen, dass dies bereits im Python-Beispiel beginnt, wo wir tatsächlich den Code simulieren, den ein Compiler zur Implementierung einer lexikalischen Variable generieren würde.
Diese Praxis ist nicht nur üblich, sondern institutionalisiert. In der OO-Welt hört man beispielsweise viel über „Muster“. Ich frage mich, ob diese Muster nicht manchmal ein Beweis für Fall (c), den menschlichen Compiler, bei der Arbeit sind. Wenn ich Muster in meinen Programmen sehe, betrachte ich das als Zeichen für Probleme. 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 einiger Makros, die 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 4 KB 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 spitzhaarigen Chef dazu bringen möchten, Sie Software in Lisp schreiben zu lassen, könnten Sie versuchen, ihm zu erzählen, es sei XML.
Hier ist der Akkumulatorgenerator in anderen Lisp-Dialekten:
Scheme: (define (foo n) (lambda (i) (set! n (+ ni)) n)) Goo: (df foo (n) (op incf n _))) Arc: (def foo (n) [++ n _])
Erann Gats traurige Geschichte über die „Best Practice der Branche“ am JPL hat mich dazu inspiriert, auf diesen allgemein falsch verwendeten Ausdruck einzugehen.
Peter Norvig stellte fest, dass 16 der 23 Muster in Design Patterns in Lisp „ unsichtbar oder einfacher “ waren.
Vielen Dank an die vielen Leute, die meine Fragen zu verschiedenen Sprachen beantwortet 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.
Verwandt:
Viele Leute haben auf diesen Vortrag reagiert, also habe ich eine zusätzliche Seite eingerichtet, um die von ihnen angesprochenen Probleme zu behandeln: Betreff: Die Rache der Eierköpfe .
Es löste auch eine ausführliche und oft nützliche Diskussion auf der LL1 -Mailingliste aus. Siehe insbesondere die E-Mail von Anton van Straaten zur semantischen Komprimierung.
Einige der Mails zu LL1 haben mich dazu veranlasst, mich in „Succinctness is Power“ eingehender mit dem Thema „Sprachmacht“ zu befassen.
Eine größere Anzahl kanonischer Implementierungen des Akkumulatorgenerator-Benchmarks ist auf einer eigenen Seite zusammengefasst.
Japanische Übersetzung ,Spanische Übersetzung , Chinesische Übersetzung