De Wraak van de Nerds

Wil je een startup beginnen? Krijg financiering van Y Combinator.


Mei 2002

| "We hadden het gemunt op de C++ programmeurs. Het lukte ons om er een hoop van hen ongeveer halverwege naar Lisp te slepen."

- Guy Steele, co-auteur van de Java-specificatie

In de softwarewereld is er een voortdurende strijd tussen de 'pointy-headed academics' en een andere, even formidabele kracht, de 'pointy-haired bosses'. Iedereen weet wie de 'pointy-haired boss' is, toch? Ik denk dat de meeste mensen in de technologiewereld dit stripfiguur niet alleen herkennen, maar ook de werkelijke persoon in hun bedrijf kennen waarop hij is gemodelleerd.

De 'pointy-haired boss' combineert op wonderbaarlijke wijze twee eigenschappen die op zichzelf veel voorkomen, maar zelden samen worden gezien: (a) hij weet niets van technologie, en (b) hij heeft er zeer sterke meningen over.

Stel je voor, bijvoorbeeld, dat je een stuk software moet schrijven. De 'pointy-haired boss' heeft geen idee hoe deze software moet werken, en kan het ene programmeertaal niet van het andere onderscheiden, en toch weet hij in welke taal je het moet schrijven. Precies. Hij denkt dat je het in Java moet schrijven.

Waarom denkt hij dat? Laten we eens in het brein van de 'pointy-haired boss' kijken. Wat hij denkt, is zoiets als dit. Java is een standaard. Dat moet het wel zijn, want ik lees er constant over in de pers. Omdat het een standaard is, krijg ik geen problemen als ik het gebruik. En dat betekent ook dat er altijd veel Java-programmeurs zullen zijn, dus als de programmeurs die nu voor me werken ontslag nemen, zoals programmeurs die voor me werken mysterieus altijd doen, kan ik ze gemakkelijk vervangen.

Nou, dit klinkt niet zo onredelijk. Maar het is allemaal gebaseerd op één onuitgesproken aanname, en die aanname blijkt onjuist te zijn. De 'pointy-haired boss' gelooft dat alle programmeertalen grotendeels equivalent zijn. Als dat waar zou zijn, zou hij helemaal gelijk hebben. Als talen allemaal equivalent zijn, ga dan zeker voor de taal die iedereen anders gebruikt.

Maar alle talen zijn niet equivalent, en ik denk dat ik je dat kan bewijzen zonder zelfs maar in te gaan op de verschillen ertussen. Als je de 'pointy-haired boss' in 1992 zou vragen in welke taal software geschreven moest worden, zou hij met even weinig aarzeling antwoorden als vandaag. Software moest in C++ geschreven worden. Maar als talen allemaal equivalent zijn, waarom zou de mening van de 'pointy-haired boss' ooit veranderen? Sterker nog, waarom zouden de ontwikkelaars van Java zich überhaupt de moeite hebben genomen om een nieuwe taal te creëren?

Vermogelijk, als je een nieuwe taal creëert, is dat omdat je denkt dat deze op de een of andere manier beter is dan wat mensen al hadden. En inderdaad, Gosling maakt duidelijk in het eerste Java white paper dat Java is ontworpen om enkele problemen met C++ op te lossen. Dus daar heb je het: talen zijn niet allemaal equivalent. Als je het spoor door het brein van de 'pointy-haired boss' naar Java volgt en dan terug door de geschiedenis van Java naar zijn oorsprong, eindig je met een idee dat de aanname waarmee je begon, tegenspreekt.

Dus, wie heeft er gelijk? James Gosling, of de 'pointy-haired boss'? Verrassend genoeg heeft Gosling gelijk. Sommige talen zijn beter, voor bepaalde problemen, dan andere. En weet je, dat roept interessante vragen op. Java is ontworpen om beter te zijn, voor bepaalde problemen, dan C++. Welke problemen? Wanneer is Java beter en wanneer is C++? Zijn er situaties waarin andere talen beter zijn dan beide?

Zodra je deze vraag begint te overwegen, heb je een echt probleem geopend. Als de 'pointy-haired boss' het probleem in zijn volledige complexiteit zou moeten overwegen, zou zijn brein ontploffen. Zolang hij alle talen als equivalent beschouwt, hoeft hij alleen degene te kiezen die de meeste momentum lijkt te hebben, en aangezien dat meer een kwestie van mode dan van technologie is, kan zelfs hij waarschijnlijk het juiste antwoord krijgen. Maar als talen variëren, moet hij plotseling twee simultane vergelijkingen oplossen, waarbij hij probeert een optimale balans te vinden tussen twee dingen waar hij niets van weet: de relatieve geschiktheid van de ongeveer twintig toonaangevende talen voor het probleem dat hij moet oplossen, en de kans op het vinden van programmeurs, bibliotheken, enz. voor elk. Als dat aan de andere kant van de deur staat, is het geen verrassing dat de 'pointy-haired boss' hem niet wil openen.

Het nadeel van geloven dat alle programmeertalen equivalent zijn, is dat het niet waar is. Maar het voordeel is dat het je leven een stuk eenvoudiger maakt. En ik denk dat dat de belangrijkste reden is waarom het idee zo wijdverbreid is. Het is een comfortabel idee.

We weten dat Java behoorlijk goed moet zijn, omdat het de coole, nieuwe programmeertaal is. Of toch niet? Als je naar de wereld van programmeertalen van een afstand kijkt, lijkt het alsof Java het nieuwste is. (Van ver genoeg weg zie je alleen de grote, knipperende reclameborden betaald door Sun.) Maar als je deze wereld van dichtbij bekijkt, ontdek je dat er gradaties van coolheid zijn. Binnen de hacker-subcultuur is er een andere taal genaamd Perl die veel cooler wordt geacht dan Java. Slashdot, bijvoorbeeld, wordt gegenereerd door Perl. Ik denk niet dat die jongens Java Server Pages zouden gebruiken. Maar er is een andere, nieuwere taal, genaamd Python, waarvan de gebruikers de neiging hebben om neer te kijken op Perl, en meer wacht in de coulissen.

Als je deze talen in volgorde bekijkt, Java, Perl, Python, merk je een interessant patroon. Althans, je merkt dit patroon als je een Lisp-hacker bent. Elk van hen is steeds meer als Lisp. Python kopieert zelfs functies die veel Lisp-hackers als fouten beschouwen. Je zou eenvoudige Lisp-programma's regel voor regel naar Python kunnen vertalen. Het is 2002, en programmeertalen hebben bijna 1958 ingehaald.

Bijblijven met Wiskunde

Wat ik bedoel is dat Lisp voor het eerst werd ontdekt door John McCarthy in 1958, en populaire programmeertalen halen nu pas de ideeën in die hij toen ontwikkelde.

Nu, hoe kan dat waar zijn? Is computertechnologie niet iets dat heel snel verandert? Ik bedoel, in 1958 waren computers koelkastgrote kolossen met de rekenkracht van een polshorloge. Hoe kan een technologie die zo oud is, überhaupt relevant zijn, laat staan superieur aan de nieuwste ontwikkelingen?

Ik zal je vertellen hoe. Dat komt omdat Lisp niet echt was ontworpen als een programmeertaal, althans niet in de zin die we vandaag de dag bedoelen. Wat we bedoelen met een programmeertaal is iets waarmee we een computer vertellen wat hij moet doen. McCarthy was uiteindelijk van plan om een programmeertaal in deze zin te ontwikkelen, maar de Lisp die we uiteindelijk kregen, was gebaseerd op iets aparts dat hij deed als theoretische oefening -- een poging om een handiger alternatief voor de Turing Machine te definiëren. Zoals McCarthy later zei:

Een andere manier om aan te tonen dat Lisp netter was dan Turing machines, was door een universele Lisp-functie te schrijven en te laten zien dat deze korter en begrijpelijker is dan de beschrijving van een universele Turing machine. Dit was de Lisp-functie eval... , die de waarde van een Lisp-expressie berekent.... Het schrijven van eval vereiste het uitvinden van een notatie die Lisp-functies vertegenwoordigt als Lisp-data, en een dergelijke notatie werd voor de doeleinden van het artikel bedacht zonder de gedachte dat deze in de praktijk zou worden gebruikt om Lisp-programma's uit te drukken. Wat daarna gebeurde, was dat ergens eind 1958 Steve Russell, een van McCarthy's promovendi, deze definitie van eval bekeek en besefte dat als hij deze naar machinetaal zou vertalen, het resultaat een Lisp-interpreter zou zijn.

Dit was destijds een grote verrassing. Hier is wat McCarthy er later over zei in een interview:

Steve Russell zei, kijk, waarom programmeer ik deze eval niet... , en ik zei tegen hem, ho, ho, je verwart theorie met praktijk, deze eval is bedoeld om te lezen, niet om te berekenen. Maar hij ging ermee aan de slag. Dat wil zeggen, hij compileerde de eval in mijn artikel naar [IBM] 704 machinetaal, maakte fouten, en adverteerde dit vervolgens als een Lisp-interpreter, wat het zeker was. Dus op dat punt had Lisp in wezen de vorm die het vandaag de dag heeft.... Plotseling, in een kwestie van weken denk ik, vond McCarthy zijn theoretische oefening getransformeerd in een daadwerkelijke programmeertaal-- en een krachtigere dan hij had bedoeld.

Dus de korte uitleg waarom deze taal uit de jaren '50 niet verouderd is, is dat het geen technologie was, maar wiskunde, en wiskunde wordt niet muf. Het juiste om Lisp mee te vergelijken is niet de hardware uit de jaren '50, maar bijvoorbeeld het Quicksort-algoritme, dat in 1960 werd ontdekt en nog steeds de snelste algemene sorteermethode is.

Er is nog één andere taal die nog steeds uit de jaren '50 overleeft, Fortran, en deze vertegenwoordigt de tegenovergestelde benadering van taalontwerp. Lisp was een stuk theorie dat onverwacht werd omgezet in een programmeertaal. Fortran werd opzettelijk ontwikkeld als een programmeertaal, maar wat we nu als een zeer laag niveau zouden beschouwen.

Fortran I, de taal die in 1956 werd ontwikkeld, was een heel ander beest dan het huidige Fortran. Fortran I was grotendeels assemblytaal met wiskunde. In sommige opzichten was het minder krachtig dan recentere assemblytalen; er waren bijvoorbeeld geen subroutines, alleen branches. Het huidige Fortran ligt nu aantoonbaar dichter bij Lisp dan bij Fortran I.

Lisp en Fortran waren de stammen van twee aparte evolutionaire bomen, de ene geworteld in wiskunde en de andere in machine-architectuur. Deze twee bomen zijn sindsdien samengevoegd. Lisp begon krachtig, en kreeg in de volgende twintig jaar snelheid. Zogenaamde mainstream-talen begonnen snel, en kregen in de volgende veertig jaar geleidelijk meer kracht, totdat de meest geavanceerde ervan nu behoorlijk dicht bij Lisp liggen. Dichtbij, maar ze missen nog steeds een paar dingen....

Wat Lisp Anders Maakte

Toen het voor het eerst werd ontwikkeld, belichaamde Lisp negen nieuwe ideeën. Sommige hiervan beschouwen we nu als vanzelfsprekend, andere worden alleen gezien in meer geavanceerde talen, en twee zijn nog steeds uniek voor Lisp. De negen ideeën zijn, in volgorde van hun adoptie door de mainstream:

  1. Conditionals. Een conditional is een if-then-else constructie. We beschouwen deze nu als vanzelfsprekend, maar Fortran I had ze niet. Het had alleen een conditionele goto die nauw gebaseerd was op de onderliggende machine-instructie.

  2. Een functietype. In Lisp zijn functies een gegevenstype, net als integers of strings. Ze hebben een letterlijke representatie, kunnen in variabelen worden opgeslagen, kunnen als argumenten worden doorgegeven, enzovoort.

  3. Recursie. Lisp was de eerste programmeertaal die het ondersteunde.

  4. Dynamische typing. In Lisp zijn alle variabelen effectief pointers. Waarden zijn wat types hebben, niet variabelen, en het toewijzen of binden van variabelen betekent het kopiëren van pointers, niet van waar ze naar verwijzen.

  5. Garbage-collection.

  6. Programma's samengesteld uit expressies. Lisp-programma's zijn bomen van expressies, die elk een waarde retourneren. Dit staat in contrast met Fortran en de meeste volgende talen, die onderscheid maken tussen expressies en statements. Het was natuurlijk om dit onderscheid te hebben in Fortran I omdat je geen statements kon nestelen. En dus, hoewel je expressies nodig had voor wiskunde om te werken, had het geen zin om iets anders een waarde te laten retourneren, omdat er niets op wachtte.

Deze beperking verdween met de komst van blok-gestructureerde talen, maar toen was het te laat. Het onderscheid tussen expressies en statements was verankerd. Het verspreidde zich van Fortran naar Algol en vervolgens naar beide hun afstammelingen.

  1. Een symbooltype. Symbolen zijn effectief pointers naar strings die in een hash-tabel zijn opgeslagen. Je kunt dus gelijkheid testen door een pointer te vergelijken, in plaats van elk teken te vergelijken.

  2. Een notatie voor code met behulp van bomen van symbolen en constanten.

  3. De hele taal was er de hele tijd. Er is geen echt onderscheid tussen read-time, compile-time en runtime. Je kunt code compileren of uitvoeren tijdens het lezen, code lezen of uitvoeren tijdens het compileren, en code lezen of compileren tijdens runtime. Het uitvoeren van code tijdens read-time stelt gebruikers in staat om de syntax van Lisp te herprogrammeren; het uitvoeren van code tijdens compile-time is de basis van macro's; het compileren tijdens runtime is de basis van het gebruik van Lisp als extensietaal in programma's zoals Emacs; en het lezen tijdens runtime stelt programma's in staat om te communiceren met behulp van s-expressies, een idee dat onlangs opnieuw is uitgevonden als XML. Toen Lisp voor het eerst verscheen, waren deze ideeën ver verwijderd van de gewone programmeerpraktijk, die grotendeels werd bepaald door de hardware die eind jaren '50 beschikbaar was. Na verloop van tijd is de standaardtaal, belichaamd in een opeenvolging van populaire talen, geleidelijk geëvolueerd naar Lisp. Ideeën 1-5 zijn nu wijdverbreid. Nummer 6 begint in de mainstream te verschijnen. Python heeft een vorm van 7, hoewel er geen syntax voor lijkt te zijn.

Wat betreft nummer 8, dit is misschien wel de meest interessante van het stel. Ideeën 8 en 9 werden pas per ongeluk deel van Lisp, omdat Steve Russell iets implementeerde dat McCarthy nooit had bedoeld te implementeren. En toch blijken deze ideeën verantwoordelijk te zijn voor zowel het vreemde uiterlijk van Lisp als zijn meest onderscheidende kenmerken. Lisp ziet er vreemd uit, niet zozeer omdat het een vreemde syntax heeft, maar omdat het geen syntax heeft; je drukt programma's rechtstreeks uit in de parse-bomen die achter de schermen worden gebouwd wanneer andere talen worden geparsed, en deze bomen zijn gemaakt van lijsten, wat Lisp-datastructuren zijn. Het uitdrukken van de taal in zijn eigen datastructuren blijkt een zeer krachtige functie te zijn. Ideeën 8 en 9 samen betekenen dat je programma's kunt schrijven die programma's schrijven. Dat klinkt misschien als een bizar idee, maar het is een alledaagse zaak in Lisp. De meest voorkomende manier om dit te doen is met iets dat een macro wordt genoemd.

De term "macro" betekent in Lisp niet wat het in andere talen betekent. Een Lisp-macro kan van alles zijn, van een afkorting tot een compiler voor een nieuwe taal. Als je Lisp echt wilt begrijpen, of gewoon je programmeerhorizon wilt verbreden, zou ik meer leren over macro's.

Macro's (in de Lisp-zin) zijn, voor zover ik weet, nog steeds uniek voor Lisp. Dit komt deels omdat je om macro's te hebben waarschijnlijk je taal er net zo vreemd uit moet laten zien als Lisp. Het kan ook komen omdat als je dat laatste beetje kracht toevoegt, je niet langer kunt beweren een nieuwe taal te hebben uitgevonden, maar slechts een nieuw dialect van Lisp.

Ik noem dit meestal als grap, maar het is heel waar. Als je een taal definieert die car, cdr, cons, quote, cond, atom, eq heeft, en een notatie voor functies uitgedrukt als lijsten, dan kun je de rest van Lisp daaruit bouwen. Dat is feitelijk de definiërende eigenschap van Lisp: het was om dit te bereiken dat McCarthy Lisp de vorm gaf die het heeft.

Waar Talen Ertoe Doen

Dus stel dat Lisp inderdaad een soort limiet vertegenwoordigt waar de mainstream-talen asymptotisch naartoe naderen - betekent dat dat je het daadwerkelijk moet gebruiken om software te schrijven? Hoeveel verlies je door een minder krachtige taal te gebruiken? Is het niet soms wijzer om niet aan de absolute voorhoede van innovatie te staan? En is populariteit niet tot op zekere hoogte een rechtvaardiging op zich? Heeft de 'pointy-haired boss' bijvoorbeeld gelijk door een taal te willen gebruiken waarvoor hij gemakkelijk programmeurs kan inhuren?

Er zijn natuurlijk projecten waarbij de keuze van de programmeertaal niet veel uitmaakt. Over het algemeen geldt: hoe veeleisender de toepassing, hoe meer voordeel je haalt uit het gebruik van een krachtige taal. Maar veel projecten zijn helemaal niet veeleisend. Het meeste programmeren bestaat waarschijnlijk uit het schrijven van kleine lijmprogramma's, en voor kleine lijmprogramma's kun je elke taal gebruiken waarmee je al bekend bent en die goede bibliotheken heeft voor wat je nodig hebt. Als je alleen gegevens van de ene Windows-app naar de andere wilt voeren, gebruik dan zeker Visual Basic.

Je kunt ook kleine lijmprogramma's schrijven in Lisp (ik gebruik het als desktop-calculator), maar de grootste winst voor talen als Lisp zit aan de andere kant van het spectrum, waar je geavanceerde programma's moet schrijven om moeilijke problemen op te lossen in het aangezicht van hevige concurrentie. Een goed voorbeeld is het airline fare search programma dat ITA Software licentieert aan Orbitz. Deze jongens kwamen een markt binnen die al werd gedomineerd door twee grote, gevestigde concurrenten, Travelocity en Expedia, en lijken hen technologisch te hebben vernederd.

De kern van ITA's applicatie is een Common Lisp-programma van 200.000 regels dat vele ordes van grootte meer mogelijkheden doorzoekt dan hun concurrenten, die blijkbaar nog steeds programmeertechnieken uit het mainframe-tijdperk gebruiken. (Hoewel ITA ook in zekere zin een programmeertaal uit het mainframe-tijdperk gebruikt.) Ik heb nog nooit code van ITA gezien, maar volgens een van hun top-hackers gebruiken ze veel macro's, en het verrast me niet om dat te horen.

Centripetale Krachten

Ik zeg niet dat het gebruik van ongebruikelijke technologieën geen kosten met zich meebrengt. De 'pointy-haired boss' vergist zich niet helemaal door zich hier zorgen over te maken. Maar omdat hij de risico's niet begrijpt, heeft hij de neiging ze te vergroten.

Ik kan drie problemen bedenken die kunnen voortvloeien uit het gebruik van minder gangbare talen. Je programma's werken mogelijk niet goed samen met programma's die in andere talen zijn geschreven. Je hebt mogelijk minder bibliotheken tot je beschikking. En je hebt mogelijk moeite met het inhuren van programmeurs.

Hoe groot is elk van deze problemen? Het belang van de eerste hangt af van of je controle hebt over het hele systeem. Als je software schrijft die moet draaien op de machine van een externe gebruiker bovenop een buggy, gesloten besturingssysteem (ik noem geen namen), zijn er mogelijk voordelen aan het schrijven van je applicatie in dezelfde taal als het OS. Maar als je het hele systeem beheert en de broncode van alle onderdelen hebt, zoals ITA vermoedelijk doet, kun je elke taal gebruiken die je wilt. Als er incompatibiliteit ontstaat, kun je die zelf oplossen.

In server-gebaseerde applicaties kun je wegkomen met het gebruik van de meest geavanceerde technologieën, en ik denk dat dit de belangrijkste oorzaak is van wat Jonathan Erickson de "programming language renaissance" noemt. Dit is waarom we zelfs horen over nieuwe talen zoals Perl en Python. We horen niet over deze talen omdat mensen ze gebruiken om Windows-apps te schrijven, maar omdat mensen ze op servers gebruiken. En naarmate software van de desktop verdwijnt en naar servers gaat (een toekomst waar zelfs Microsoft zich bij lijkt neer te leggen), zal er steeds minder druk zijn om midden-in-de-weg technologieën te gebruiken.

Wat bibliotheken betreft, hangt hun belang ook af van de toepassing. Voor minder veeleisende problemen kan de beschikbaarheid van bibliotheken zwaarder wegen dan de intrinsieke kracht van de taal. Waar is het omslagpunt? Moeilijk exact te zeggen, maar waar het ook is, het is minder dan wat je waarschijnlijk een applicatie zou noemen. Als een bedrijf zichzelf beschouwt als actief in de softwarebusiness, en ze schrijven een applicatie die een van hun producten zal zijn, dan zal het waarschijnlijk verschillende hackers omvatten en minstens zes maanden duren om te schrijven. In een project van die omvang beginnen krachtige talen waarschijnlijk de voorkeur te krijgen boven het gemak van vooraf bestaande bibliotheken.

De derde zorg van de 'pointy-haired boss', de moeilijkheid om programmeurs in te huren, is naar mijn mening een rode haring. Hoeveel hackers moet je tenslotte inhuren? Zeker weten we nu allemaal dat software het best wordt ontwikkeld door teams van minder dan tien personen. En je zou geen moeite moeten hebben om op die schaal hackers in te huren voor elke taal waar iemand ooit van heeft gehoord. Als je geen tien Lisp-hackers kunt vinden, is je bedrijf waarschijnlijk gevestigd in de verkeerde stad voor softwareontwikkeling.

Sterker nog, het kiezen van een krachtigere taal vermindert waarschijnlijk de omvang van het team dat je nodig hebt, omdat (a) als je een krachtigere taal gebruikt, je waarschijnlijk minder hackers nodig hebt, en (b) hackers die in meer geavanceerde talen werken, waarschijnlijk slimmer zijn.

Ik zeg niet dat je geen druk zult ervaren om wat wordt beschouwd als "standaard" technologieën te gebruiken. Bij Viaweb (nu Yahoo Store) trokken we de wenkbrauwen op bij VCs en potentiële overnemers door Lisp te gebruiken. Maar we trokken ook de wenkbrauwen op door generieke Intel-servers te gebruiken in plaats van "industriële" servers zoals Suns, door een toen obscuur open-source Unix-variant genaamd FreeBSD te gebruiken in plaats van een echt commercieel OS zoals Windows NT, door een vermeende e-commerce standaard genaamd SET te negeren die niemand zich nu nog herinnert, enzovoort.

Je kunt de pakken niet de technische beslissingen voor je laten nemen. Heeft het sommige potentiële overnemers gealarmeerd dat we Lisp gebruikten? Sommigen, lichtelijk, maar als we Lisp niet hadden gebruikt, hadden we de software niet kunnen schrijven die hen ertoe bracht ons te willen kopen. Wat voor hen een anomalie leek, was in feite oorzaak en gevolg.

Als je een startup begint, ontwerp je product dan niet om VCs of potentiële overnemers te plezieren. Ontwerp je product om de gebruikers te plezieren. Als je de gebruikers wint, volgt al het andere. En als je dat niet doet, zal niemand zich bekommeren om hoe troostend orthodox je technologische keuzes waren.

De Prijs van Gemiddeld Zijn

Hoeveel verlies je door een minder krachtige taal te gebruiken? Er zijn feitelijk gegevens over.

De meest handige maatstaf voor kracht is waarschijnlijk codeomvang. Het punt van high-level talen is om je grotere abstracties te geven - grotere bakstenen, als het ware, zodat je er niet veel nodig hebt om een muur van een bepaalde grootte te bouwen. Dus hoe krachtiger de taal, hoe korter het programma (niet simpelweg in tekens, natuurlijk, maar in afzonderlijke elementen).

Hoe stelt een krachtigere taal je in staat om kortere programma's te schrijven? Een techniek die je kunt gebruiken, als de taal het toestaat, is iets dat bottom-up programmeren wordt genoemd. In plaats van simpelweg je applicatie in de basistaal te schrijven, bouw je bovenop de basistaal een taal om programma's zoals de jouwe te schrijven, en schrijf je je programma erin. De gecombineerde code kan veel korter zijn dan wanneer je je hele programma in de basistaal had geschreven - sterker nog, dit is hoe de meeste compressie-algoritmen werken. Een bottom-up programma zou ook gemakkelijker te wijzigen moeten zijn, omdat in veel gevallen de taallaag helemaal niet hoeft te veranderen.

Codeomvang is belangrijk, omdat de tijd die nodig is om een programma te schrijven grotendeels afhangt van de lengte ervan. Als je programma drie keer zo lang zou zijn in een andere taal, zal het drie keer zo lang duren om te schrijven - en je kunt dit niet omzeilen door meer mensen in te huren, omdat nieuwe aanwervingen boven een bepaalde omvang feitelijk een nettoverlies zijn. Fred Brooks beschreef dit fenomeen in zijn beroemde boek The Mythical Man-Month, en alles wat ik heb gezien, heeft de neiging te bevestigen wat hij zei.

Dus hoe veel korter zijn je programma's als je ze in Lisp schrijft? De meeste getallen die ik heb gehoord voor Lisp versus C, bijvoorbeeld, waren rond de 7-10x. Maar een recent artikel over ITA in New Architect magazine zei dat "één regel Lisp 20 regels C kan vervangen", en aangezien dit artikel vol stond met citaten van de president van ITA, ga ik ervan uit dat ze dit getal van ITA hebben. Zo ja, dan kunnen we daar vertrouwen in hebben; ITA's software bevat veel C en C++ naast Lisp, dus ze spreken uit ervaring.

Mijn gok is dat deze veelvouden niet eens constant zijn. Ik denk dat ze toenemen wanneer je moeilijkere problemen tegenkomt en ook wanneer je slimmere programmeurs hebt. Een echt goede hacker kan meer uit betere tools halen.

Als een datapunt op de curve, in ieder geval, als je ITA zou concurreren en ervoor zou kiezen om je software in C te schrijven, zouden zij twintig keer sneller software kunnen ontwikkelen dan jij. Als je een jaar aan een nieuwe functie zou besteden, zouden zij die in minder dan drie weken kunnen dupliceren. Terwijl als zij slechts drie maanden zouden besteden aan het ontwikkelen van iets nieuws, het vijf jaar zou duren voordat jij het ook had.

En weet je wat? Dat is het best-case scenario. Als je het hebt over code-omvangverhoudingen, ga je er impliciet van uit dat je het programma daadwerkelijk in de zwakkere taal kunt schrijven. Maar in feite zijn er grenzen aan wat programmeurs kunnen doen. Als je een moeilijk probleem probeert op te lossen met een taal die te laag-niveau is, bereik je een punt waarop er gewoon te veel is om tegelijkertijd in je hoofd te houden.

Dus als ik zeg dat het de imaginaire concurrent van ITA vijf jaar zou kosten om iets te dupliceren dat ITA in drie maanden in Lisp zou kunnen schrijven, bedoel ik vijf jaar als er niets misgaat. In feite, zoals dingen werken in de meeste bedrijven, is elk ontwikkelingsproject dat vijf jaar zou duren waarschijnlijk nooit voltooid.

Ik geef toe dat dit een extreem geval is. ITA's hackers lijken ongewoon slim, en C is een behoorlijk laag-niveau taal. Maar in een competitieve markt zou zelfs een verschil van twee of drie tot één genoeg zijn om te garanderen dat je altijd achter zou lopen.

Een Recept

Dit is het soort mogelijkheid waar de 'pointy-haired boss' niet eens aan wil denken. En dus doen de meesten van hen dat niet. Want, weet je, als het erop aankomt, vindt de 'pointy-haired boss' het niet erg als zijn bedrijf een pak slaag krijgt, zolang niemand kan bewijzen dat het zijn schuld is. Het veiligste plan voor hem persoonlijk is om dicht bij het midden van de kudde te blijven.

Binnen grote organisaties is de uitdrukking die wordt gebruikt om deze aanpak te beschrijven "industry best practice" (de beste praktijk in de branche). Het doel is om de 'pointy-haired boss' te beschermen tegen verantwoordelijkheid: als hij iets kiest dat "industry best practice" is, en het bedrijf verliest, kan hij niet de schuld krijgen. Hij koos niet, de branche deed het.

Ik geloof dat deze term oorspronkelijk werd gebruikt om boekhoudmethoden enzovoort te beschrijven. Wat het ruwweg betekent, is doe niets vreemds. En in de boekhouding is dat waarschijnlijk een goed idee. De termen "cutting-edge" en "boekhouding" klinken niet goed samen. Maar wanneer je dit criterium importeert in beslissingen over technologie, begin je de verkeerde antwoorden te krijgen.

Technologie zou vaak cutting-edge moeten zijn. In programmeertalen, zoals Erann Gat heeft opgemerkt, krijg je met "industry best practice" feitelijk niet het beste, maar slechts het gemiddelde. Wanneer een beslissing ertoe leidt dat je software ontwikkelt tegen een fractie van de snelheid van agressievere concurrenten, is "best practice" een verkeerde benaming.

Dus hier hebben we twee stukken informatie die naar mijn mening erg waardevol zijn. Sterker nog, ik weet het uit eigen ervaring. Nummer 1, talen variëren in kracht. Nummer 2, de meeste managers negeren dit bewust. Tussen deze twee feiten zijn letterlijk een recept om geld te verdienen. ITA is een voorbeeld van dit recept in actie. Als je wilt winnen in de softwarebusiness, pak dan gewoon het moeilijkste probleem dat je kunt vinden aan, gebruik de krachtigste taal die je kunt krijgen, en wacht tot de 'pointy-haired bosses' van je concurrenten terugkeren naar het gemiddelde.


Bijlage: Kracht

Als illustratie van wat ik bedoel met de relatieve kracht van programmeertalen, overweeg het volgende probleem. We willen een functie schrijven die accumulatoren genereert - een functie die een getal n neemt, en een functie retourneert die een ander getal i neemt en n plus i retourneert.

(Dat is plus, niet vermenigvuldigd. Een accumulator moet accumuleren.)

In Common Lisp zou dit zijn (defun foo (n) (lambda (i) (incf n i))) en in Perl 5, sub foo { my ($n) = @_; sub {$n += shift} } wat meer elementen heeft dan de Lisp-versie omdat je parameters handmatig moet extraheren in Perl.

In Smalltalk is de code iets langer dan in Lisp foo: n |s| s := n. ^[:i| s := s+i. ] omdat, hoewel in het algemeen lexicale variabelen werken, je geen toewijzing aan een parameter kunt doen, dus je moet een nieuwe variabele s maken.

In Javascript is het voorbeeld opnieuw iets langer, omdat Javascript het onderscheid tussen statements en expressies behoudt, dus je hebt expliciete return statements nodig om waarden te retourneren: function foo(n) { return function (i) { return n += i } } (Om eerlijk te zijn, Perl behoudt ook dit onderscheid, maar gaat er op typische Perl-manier mee om door je returns te laten weglaten.)

Als je probeert de Lisp/Perl/Smalltalk/Javascript-code naar Python te vertalen, loop je tegen enkele beperkingen aan. Omdat Python lexicale variabelen niet volledig ondersteunt, moet je een datastructuur maken om de waarde van n op te slaan. En hoewel Python wel een functiedatatype heeft, is er geen letterlijke representatie voor (tenzij de body slechts één expressie is), dus je moet een benoemde functie maken om te retourneren. Dit is wat je krijgt: def foo(n): s = [n] def bar(i): s[0] += i return s[0] return bar Python-gebruikers kunnen legitiem vragen waarom ze niet gewoon kunnen schrijven def foo(n): return lambda i: return n += i of zelfs def foo(n): lambda i: n += i en mijn gok is dat ze dat waarschijnlijk wel zullen doen, op een dag. (Maar als ze niet willen wachten tot Python evolueert naar Lisp, kunnen ze altijd gewoon...)

In OO-talen kun je, tot op zekere hoogte, een closure (een functie die verwijst naar variabelen gedefinieerd in omringende scopes) simuleren door een klasse te definiëren met één methode en een veld om elke variabele uit een omringende scope te vervangen. Dit zorgt ervoor dat de programmeur de soort code-analyse uitvoert die door de compiler zou worden gedaan in een taal met volledige ondersteuning voor lexicale scope, en het werkt niet als meer dan één functie naar dezelfde variabele verwijst, maar het is genoeg in eenvoudige gevallen zoals dit. Python-experts lijken het erover eens te zijn dat dit de voorkeursmanier is om het probleem in Python op te lossen, waarbij ze het volgende schrijven: 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 of class foo: def init(self, n): self.n = n def call(self, i): self.n += i return self.n Ik neem deze op omdat ik niet wil dat Python-voorstanders zeggen dat ik de taal verkeerd voorstel, maar beide lijken mij complexer dan de eerste versie. Je doet hetzelfde, je zet een aparte plek op om de accumulator te bewaren; het is gewoon een veld in een object in plaats van het hoofd van een lijst. En het gebruik van deze speciale, gereserveerde veldnamen, vooral __call__, lijkt een beetje een hack.

In de rivaliteit tussen Perl en Python, lijkt de claim van de Python-hackers te zijn dat Python een eleganter alternatief is voor Perl, maar wat dit geval laat zien, is dat kracht de ultieme elegantie is: het Perl-programma is eenvoudiger (heeft minder elementen), zelfs als de syntax een beetje lelijker is. Hoe zit het met andere talen? In de andere talen die in deze talk worden genoemd - Fortran, C, C++, Java en Visual Basic - is het niet duidelijk of je dit probleem daadwerkelijk kunt oplossen. Ken Anderson zegt dat de volgende code ongeveer het dichtst in de buurt komt in Java:

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;
    }};
}

Dit voldoet niet aan de specificatie omdat het alleen werkt voor integers. Na vele e-mailuitwisselingen met Java-hackers, zou ik zeggen dat het schrijven van een correct polymorfe versie die zich gedraagt als de voorgaande voorbeelden ergens tussen verdomd onhandig en onmogelijk is. Als iemand er een wil schrijven, zou ik erg benieuwd zijn om het te zien, maar ik heb persoonlijk de handdoek in de ring gegooid.

Het is natuurlijk niet letterlijk waar dat je dit probleem niet in andere talen kunt oplossen. Het feit dat al deze talen Turing-equivalent zijn, betekent dat je, strikt genomen, elk programma in elk van hen kunt schrijven. Dus hoe zou je het doen? In het grensgeval, door een Lisp-interpreter te schrijven in de minder krachtige taal.

Dat klinkt als een grap, maar het gebeurt zo vaak in verschillende mate in grote programmeerprojecten dat er een naam voor het fenomeen is, Greenspun's Tenth Rule:

Elke voldoende gecompliceerde C- of Fortran-programma bevat een ad hoc informeel gespecificeerde, met bugs bezaaide, langzame implementatie van de helft van Common Lisp. Als je een moeilijk probleem probeert op te lossen, is de vraag niet of je een krachtige genoeg taal zult gebruiken, maar of je (a) een krachtige taal zult gebruiken, (b) een de facto interpreter ervoor zult schrijven, of (c) zelf een menselijke compiler ervoor zult worden. We zien dit al beginnen te gebeuren in het Python-voorbeeld, waar we effectief de code simuleren die een compiler zou genereren om een lexicale variabele te implementeren.

Deze praktijk is niet alleen gebruikelijk, maar ook geïnstitutionaliseerd. Bijvoorbeeld, in de OO-wereld hoor je veel over "patterns". Ik vraag me af of deze patronen niet soms bewijs zijn van geval (c), de menselijke compiler, aan het werk. Als ik patronen in mijn programma's zie, beschouw ik het als een teken van problemen. De vorm van een programma moet alleen het probleem weerspiegelen dat het moet oplossen. Elke andere regelmatigheid in de code is een teken, althans voor mij, dat ik abstracties gebruik die niet krachtig genoeg zijn - vaak dat ik handmatig de uitbreidingen van een macro genereer die ik moet schrijven.

Noten

  • De IBM 704 CPU was ongeveer zo groot als een koelkast, maar veel zwaarder. De CPU woog 3150 pond, en de 4K RAM zat in een aparte doos die nog eens 4000 pond woog. De Sub-Zero 690, een van de grootste huishoudelijke koelkasten, weegt 656 pond.

  • Steve Russell schreef ook het eerste (digitale) computerspel, Spacewar, in 1962.

  • Als je een 'pointy-haired boss' wilt overhalen om je software in Lisp te laten schrijven, kun je proberen hem te vertellen dat het XML is.

  • Hier is de accumulator generator in andere Lisp-dialecten: Scheme: (define (foo n) (lambda (i) (set! n (+ n i)) n)) Goo: (df foo (n) (op incf n _))) Arc: (def foo (n) [++ n _])

  • Erann Gat's trieste verhaal over "industry best practice" bij JPL inspireerde me om dit algemeen verkeerd toegepaste jargon aan te pakken.

  • Peter Norvig ontdekte dat 16 van de 23 patronen in Design Patterns "onzichtbaar of eenvoudiger" waren in Lisp.

  • Dank aan de vele mensen die mijn vragen over verschillende talen hebben beantwoord en/of conceptversies hiervan hebben gelezen, waaronder Ken Anderson, Trevor Blackwell, Erann Gat, Dan Giffin, Sarah Harlin, Jeremy Hylton, Robert Morris, Peter Norvig, Guy Steele en Anton van Straaten. Zij dragen geen schuld voor de geuite meningen.

Gerelateerd:

Veel mensen hebben gereageerd op deze talk, dus ik heb een extra pagina opgezet om de door hen aangekaarte kwesties te behandelen: Re: Revenge of the Nerds.

Het zette ook een uitgebreide en vaak nuttige discussie in gang op de LL1 mailinglijst. Zie met name de mail van Anton van Straaten over semantische compressie.

Sommige mail op LL1 deed me proberen dieper in te gaan op het onderwerp taalsterkte in Succinctness is Power.

Een grotere set canonieke implementaties van de accumulator generator benchmark is verzameld op een eigen pagina.

Japanse Vertaling, Spaanse Vertaling, Chinese Vertaling