Język stuletni

Kwiecień 2003

(Niniejszy esej jest oparty na wykładzie inauguracyjnym wygłoszonym na PyCon 2003.)

Trudno przewidzieć, jak będzie wyglądało życie za sto lat. Możemy z pewnością powiedzieć tylko kilka rzeczy. Wiemy, że wszyscy będą jeździć latającymi samochodami, że przepisy dotyczące zagospodarowania przestrzennego zostaną złagodzone, aby umożliwić budowę budynków o wysokości setek pięter, że przez większość czasu będzie ciemno, a kobiety będą przeszkolone ze sztuk walki. Tutaj chcę się skupić na jednym szczególe tego obrazu. Jakiego rodzaju języka programowania będą używać do pisania oprogramowania sterującego tymi latającymi samochodami?

Warto o tym pomyśleć nie tyle dlatego, że faktycznie będziemy używać tych języków, co dlatego, że jeśli będziemy mieli szczęście, będziemy używać języków na ścieżce od tego punktu do tamtego.

Myślę, że podobnie jak gatunki, języki będą tworzyć drzewa ewolucyjne, z martwymi odgałęzieniami wszędzie. Już teraz możemy to zaobserwować. Cobol, pomimo swojej niegdyś popularności, nie wydaje się mieć żadnych intelektualnych potomków. Jest to martwe odgałęzienie ewolucyjne – język neandertalski.

Przewiduję podobny los dla Javy. Ludzie czasami wysyłają mi e-maile, mówiąc: „Jak możesz mówić, że Java nie okaże się językiem odnoszącym sukcesy? To już jest sukces”. Przyznaję, że tak jest, jeśli sukces mierzy się ilością miejsca na półkach zajmowanego przez książki na jej temat (zwłaszcza pojedyncze książki na jej temat) lub liczbą studentów, którzy wierzą, że muszą się jej nauczyć, aby zdobyć pracę. Kiedy mówię, że Java nie okaże się językiem odnoszącym sukcesy, mam na myśli coś bardziej konkretnego: że Java okaże się martwym odgałęzieniem ewolucyjnym, jak Cobol.

To tylko przypuszczenie. Mogę się mylić. Nie chodzi mi o dyskredytowanie Javy, ale o poruszenie kwestii drzew ewolucyjnych i skłonienie ludzi do zadawania pytań: gdzie na drzewie znajduje się język X? Powodem zadawania tego pytania nie jest tylko to, aby nasze duchy mogły powiedzieć za sto lat: „Mówiłem wam”. Chodzi o to, że pozostawanie blisko głównych gałęzi jest użyteczną heurystyką do znajdowania języków, w których warto programować teraz.

W danym momencie prawdopodobnie będziesz najszczęśliwszy na głównych gałęziach drzewa ewolucyjnego. Nawet gdy wciąż było wielu neandertalczyków, musieli mieć przechlapane. Kro-manińczycy musieliby ich ciągle bić i kraść im jedzenie.

Powodem, dla którego chcę wiedzieć, jak będą wyglądać języki za sto lat, jest to, abym wiedział, na którą gałąź drzewa postawić teraz.

Ewolucja języków różni się od ewolucji gatunków tym, że gałęzie mogą się zbiegać. Gałąź Fortranu, na przykład, wydaje się łączyć z potomkami Algola. Teoretycznie jest to możliwe również dla gatunków, ale prawdopodobnie nie dotyczyło to niczego większego niż komórka.

Zbieżność jest bardziej prawdopodobna dla języków częściowo dlatego, że przestrzeń możliwości jest mniejsza, a częściowo dlatego, że mutacje nie są losowe. Projektanci języków celowo wprowadzają pomysły z innych języków.

Jest to szczególnie przydatne dla projektantów języków, aby zastanowić się, dokąd doprowadzi ewolucja języków programowania, ponieważ mogą oni odpowiednio kierować tym procesem. W takim przypadku „pozostawanie na głównej gałęzi” staje się czymś więcej niż tylko sposobem wyboru dobrego języka. Staje się heurystyką do podejmowania właściwych decyzji dotyczących projektowania języka.

Każdy język programowania można podzielić na dwie części: pewien zestaw podstawowych operatorów, które pełnią rolę aksjomatów, i resztę języka, która w zasadzie mogłaby być napisana w oparciu o te podstawowe operatory.

Myślę, że podstawowe operatory są najważniejszym czynnikiem długoterminowego przetrwania języka. Resztę można zmienić. To jak zasada, że kupując dom, należy przede wszystkim wziąć pod uwagę lokalizację. Wszystko inne można później naprawić, ale lokalizacji nie.

Myślę, że ważne jest nie tylko to, aby aksjomaty były dobrze dobrane, ale także to, aby było ich niewiele. Matematycy zawsze tak myśleli o aksjomatach – im mniej, tym lepiej – i myślę, że coś w tym jest.

Przynajmniej ćwiczenie polegające na dokładnym przyjrzeniu się rdzeniowi języka, aby sprawdzić, czy nie ma aksjomatów, które można by wyeliminować, jest pożyteczne. W mojej długiej karierze jako bałaganiarza odkryłem, że bałagan rodzi bałagan, i widziałem to zarówno w oprogramowaniu, jak i pod łóżkami oraz w kątach pokoi.

Mam przeczucie, że główne gałęzie drzewa ewolucyjnego przechodzą przez języki o najmniejszych, najczystszych rdzeniach. Im więcej języka można napisać w nim samym, tym lepiej.

Oczywiście, czynię duże założenie, zadając nawet pytanie, jak będą wyglądać języki programowania za sto lat. Czy w ogóle będziemy pisać programy za sto lat? Czy po prostu powiemy komputerom, czego od nich chcemy?

Jak dotąd nie poczyniono w tej dziedzinie wielu postępów. Moim zdaniem za sto lat ludzie nadal będą mówić komputerom, co mają robić, używając programów, które byśmy rozpoznali jako takie. Mogą istnieć zadania, które rozwiązujemy teraz, pisząc programy, a które za sto lat nie będą wymagały pisania programów, ale myślę, że nadal będzie istniała spora część programowania tego typu, które wykonujemy dzisiaj.

Może się wydawać zuchwałe myśleć, że ktokolwiek może przewidzieć, jak będzie wyglądać jakakolwiek technologia za sto lat. Ale pamiętajmy, że mamy już za sobą prawie pięćdziesiąt lat historii. Patrzenie sto lat w przyszłość jest uchwytną ideą, biorąc pod uwagę, jak wolno ewoluowały języki w ciągu ostatnich pięćdziesięciu lat.

Języki ewoluują powoli, ponieważ nie są one tak naprawdę technologiami. Języki to notacja. Program to formalny opis problemu, który chcesz, aby komputer rozwiązał dla Ciebie. Zatem tempo ewolucji języków programowania jest bardziej zbliżone do tempa ewolucji notacji matematycznej niż, powiedzmy, transportu czy komunikacji. Notacja matematyczna ewoluuje, ale nie z takimi skokami, jakie widzimy w technologii.

Niezależnie od tego, z czego będą wykonane komputery za sto lat, można bezpiecznie przewidzieć, że będą znacznie szybsze niż obecnie. Jeśli prawo Moore'a będzie nadal obowiązywać, będą one 74 kwintyliony (73 786 976 294 838 206 464) razy szybsze. To trudne do wyobrażenia. I rzeczywiście, najbardziej prawdopodobnym przewidywaniem w dziedzinie prędkości może być to, że prawo Moore'a przestanie działać. Wszystko, co ma się podwajać co osiemnaście miesięcy, prawdopodobnie ostatecznie napotka jakiś fundamentalny limit. Ale nie mam problemu z uwierzeniem, że komputery będą znacznie szybsze. Nawet jeśli okażą się tylko marnie milion razy szybsze, powinno to znacząco zmienić zasady gry dla języków programowania. Między innymi będzie więcej miejsca na to, co obecnie uważano by za wolne języki, czyli języki, które nie generują bardzo wydajnego kodu.

A jednak niektóre zastosowania nadal będą wymagać szybkości. Niektóre z problemów, które chcemy rozwiązać za pomocą komputerów, są tworzone przez komputery; na przykład tempo, w jakim musisz przetwarzać obrazy wideo, zależy od tempa, w jakim inny komputer może je generować. Istnieje też inna klasa problemów, które z natury mają nieograniczoną zdolność do pochłaniania cykli: renderowanie obrazów, kryptografia, symulacje.

Jeśli niektóre zastosowania mogą być coraz bardziej nieefektywne, podczas gdy inne nadal będą wymagać całej szybkości, jaką sprzęt może dostarczyć, szybsze komputery oznaczają, że języki będą musiały obejmować coraz szerszy zakres wydajności. Już to widzimy. Obecne implementacje niektórych popularnych nowych języków są szokująco niegospodarne według standardów poprzednich dekad.

To nie jest tylko coś, co dzieje się z językami programowania. To ogólny trend historyczny. W miarę postępu technologii każde pokolenie może robić rzeczy, które poprzednie pokolenie uważałoby za marnotrawstwo. Ludzie trzydzieści lat temu byliby zdumieni tym, jak swobodnie wykonujemy rozmowy telefoniczne na odległość. Ludzie sto lat temu byliby jeszcze bardziej zdumieni, że paczka pewnego dnia podróżuje z Bostonu do Nowego Jorku przez Memphis.

Mogę już powiedzieć, co stanie się ze wszystkimi dodatkowymi cyklami, które szybszy sprzęt zapewni nam w ciągu najbliższych stu lat. Prawie wszystkie zostaną zmarnowane.

Nauczyłem się programować, gdy moc obliczeniowa była ograniczona. Pamiętam, jak usuwałem wszystkie spacje z moich programów Basic, aby zmieściły się w pamięci 4K TRS-80. Myśl o tym, jak nieefektywne oprogramowanie spala cykle, wykonując te same czynności w kółko, wydaje mi się trochę obrzydliwa. Ale myślę, że moje intuicje są tu błędne. Jestem jak ktoś, kto dorastał w biedzie i nie może znieść wydawania pieniędzy, nawet na coś ważnego, jak wizyta u lekarza.

Niektóre rodzaje marnotrawstwa są naprawdę obrzydliwe. SUV-y, na przykład, byłyby prawdopodobnie obrzydliwe nawet wtedy, gdyby działały na paliwie, które nigdy by się nie skończyło i nie generowałyby zanieczyszczeń. SUV-y są obrzydliwe, ponieważ są rozwiązaniem obrzydliwego problemu. (Jak sprawić, by minivany wyglądały bardziej męsko.) Ale nie wszystkie marnotrawstwa są złe. Teraz, gdy mamy infrastrukturę, aby je wspierać, liczenie minut rozmów telefonicznych na odległość wydaje się drobiazgowe. Jeśli masz zasoby, bardziej elegancko jest traktować wszystkie rozmowy telefoniczne jako jeden rodzaj rzeczy, niezależnie od tego, gdzie znajduje się druga osoba.

Istnieje dobre i złe marnotrawstwo. Interesuje mnie dobre marnotrawstwo – takie, w którym wydając więcej, możemy uzyskać prostsze projekty. Jak wykorzystamy możliwości marnowania cykli, które uzyskamy dzięki nowemu, szybszemu sprzętowi?

Pragnienie szybkości jest tak głęboko zakorzenione w nas, przy naszych marnych komputerach, że przezwyciężenie go będzie wymagało świadomego wysiłku. W projektowaniu języków powinniśmy świadomie szukać sytuacji, w których możemy wymienić wydajność na nawet najmniejszy wzrost wygody.

Większość struktur danych istnieje ze względu na szybkość. Na przykład wiele języków ma dziś zarówno ciągi znaków, jak i listy. Semantycznie ciągi znaków są mniej więcej podzbiorem list, w których elementy są znakami. Dlaczego więc potrzebujesz oddzielnego typu danych? Naprawdę nie.

Ciągi znaków istnieją tylko dla wydajności. Ale jest słabe, aby zaśmiecać semantykę języka poprawkami mającymi na celu przyspieszenie działania programów. Posiadanie ciągów znaków w języku wydaje się przypadkiem przedwczesnej optymalizacji.

Jeśli potraktujemy rdzeń języka jako zbiór aksjomatów, z pewnością jest obrzydliwe mieć dodatkowe aksjomaty, które nie dodają żadnej mocy ekspresji, tylko dla oszczędności. Wydajność jest ważna, ale nie sądzę, że to właściwy sposób, aby ją uzyskać.

Właściwym sposobem rozwiązania tego problemu jest oddzielenie znaczenia programu od szczegółów implementacji. Zamiast mieć zarówno listy, jak i ciągi znaków, miej tylko listy, z możliwością udzielenia kompilatorowi wskazówek optymalizacyjnych, które pozwolą mu ułożyć ciągi znaków jako ciągłe bajty, jeśli to konieczne.

Ponieważ szybkość nie ma znaczenia w większości programu, zazwyczaj nie będziesz musiał martwić się o takie mikrozarządzanie. Będzie to coraz bardziej prawdziwe w miarę postępu komputerów.

Mówienie mniej o implementacji powinno również sprawić, że programy będą bardziej elastyczne. Specyfikacje zmieniają się podczas pisania programu, i to jest nie tylko nieuniknione, ale i pożądane.

Słowo „esej” pochodzi od francuskiego czasownika „essayer”, który oznacza „próbować”. Esej, w pierwotnym znaczeniu, to coś, co piszesz, aby coś rozgryźć. Tak jest również w oprogramowaniu. Myślę, że niektóre z najlepszych programów były esejami, w tym sensie, że autorzy nie wiedzieli na początku, co dokładnie próbują napisać.

Hakerzy Lisp już wiedzą o wartości elastyczności w strukturach danych. Zwykle piszemy pierwszą wersję programu tak, aby wszystko robiła z listami. Te początkowe wersje mogą być tak szokująco nieefektywne, że wymaga to świadomego wysiłku, aby nie myśleć o tym, co robią, tak jak dla mnie przynajmniej zjedzenie steku wymaga świadomego wysiłku, aby nie myśleć, skąd się wziął.

Programiści za sto lat będą szukać przede wszystkim języka, w którym można z najmniejszym wysiłkiem połączyć niewiarygodnie nieefektywną wersję 1 programu. Przynajmniej tak byśmy to opisali dzisiejszymi terminami. Oni powiedzą, że chcą języka, w którym łatwo się programuje.

Nieefektywne oprogramowanie nie jest obrzydliwe. Obrzydliwe jest to, że język zmusza programistów do niepotrzebnej pracy. Marnowanie czasu programisty jest prawdziwą nieefektywnością, a nie marnowanie czasu maszyny. To będzie coraz jaśniejsze w miarę postępu komputerów.

Myślę, że pozbycie się ciągów znaków jest już czymś, o czym moglibyśmy pomyśleć. Zrobiliśmy to w Arc, i wydaje się, że to się opłaciło; niektóre operacje, które byłyby niezręczne do opisania jako wyrażenia regularne, można łatwo opisać jako funkcje rekurencyjne.

Jak daleko sięgnie to spłaszczenie struktur danych? Mogę myśleć o możliwościach, które szokują nawet mnie, z moim sumiennie poszerzonym umysłem. Czy pozbędziemy się na przykład tablic? W końcu są one tylko podzbiorem tablic mieszających, w których kluczami są wektory liczb całkowitych. Czy zastąpimy same tablice mieszające listami?

Istnieją jeszcze bardziej szokujące perspektywy. Na przykład Lisp, który McCarthy opisał w 1960 roku, nie miał liczb. Logicznie rzecz biorąc, nie trzeba mieć oddzielnego pojęcia liczb, ponieważ można je reprezentować jako listy: liczbę całkowitą n można reprezentować jako listę n elementów. Można w ten sposób wykonywać obliczenia. Jest to po prostu nieznośnie nieefektywne.

Nikt tak naprawdę nie proponował implementacji liczb jako list w praktyce. W rzeczywistości artykuł McCarthy'ego z 1960 roku nie był w tamtym czasie przeznaczony do implementacji. Był to teoretyczny ćwiczenie, próba stworzenia bardziej eleganckiej alternatywy dla maszyny Turinga. Kiedy ktoś, wbrew oczekiwaniom, wziął ten artykuł i przetłumaczył go na działający interpreter Lisp, liczby z pewnością nie były reprezentowane jako listy; były reprezentowane w systemie binarnym, jak w każdym innym języku.

Czy język programowania mógłby pójść tak daleko, aby pozbyć się liczb jako podstawowego typu danych? Pytam o to nie tyle jako o poważne pytanie, co jako o sposób na zabawę w kotka i myszkę z przyszłością. To jak hipotetyczny przypadek niepowstrzymanej siły spotykającej nieprzesuwalny obiekt – tutaj niewyobrażalnie nieefektywna implementacja spotykająca niewyobrażalnie wielkie zasoby. Nie widzę powodu, dla którego nie. Przyszłość jest dość długa. Jeśli jest coś, co możemy zrobić, aby zmniejszyć liczbę aksjomatów w podstawowym języku, to wydaje się, że to jest strona, na którą warto postawić, gdy t dąży do nieskończoności. Jeśli pomysł nadal będzie nie do zniesienia za sto lat, być może za tysiąc lat będzie.

Żeby było jasne, nie proponuję, aby wszystkie obliczenia numeryczne były faktycznie przeprowadzane za pomocą list. Proponuję, aby podstawowy język, przed wszelkimi dodatkowymi notacjami dotyczącymi implementacji, był tak zdefiniowany. W praktyce każdy program, który chciałby wykonać jakąkolwiek ilość obliczeń, prawdopodobnie reprezentowałby liczby w systemie binarnym, ale byłaby to optymalizacja, a nie część semantyki podstawowego języka.

Innym sposobem na spalanie cykli jest posiadanie wielu warstw oprogramowania między aplikacją a sprzętem. To również jest trend, który już obserwujemy: wiele ostatnich języków jest kompilowanych do kodu bajtowego. Bill Woods powiedział mi kiedyś, że jako zasadę, każda warstwa interpretacji kosztuje czynnik 10 w szybkości. Ten dodatkowy koszt zapewnia elastyczność.

Pierwsza wersja Arc była ekstremalnym przypadkiem tego rodzaju wielopoziomowej powolności, z odpowiednimi korzyściami. Był to klasyczny interpreter „metacircularny” napisany na Common Lisp, z wyraźnym podobieństwem rodzinnym do funkcji eval zdefiniowanej w oryginalnym artykule Lisp McCarthy'ego. Całość miała tylko kilkaset linii kodu, więc była bardzo łatwa do zrozumienia i zmiany. Używany przez nas Common Lisp, CLisp, sam działa na interpreterze kodu bajtowego. Mieliśmy więc dwa poziomy interpretacji, z których jeden (górny) był szokująco nieefektywny, a język był użyteczny. Ledwo użyteczny, przyznaję, ale użyteczny.

Pisanie oprogramowania w wielu warstwach jest potężną techniką nawet w ramach aplikacji. Programowanie od dołu do góry oznacza pisanie programu jako serii warstw, z których każda służy jako język dla warstwy wyższej. Takie podejście zazwyczaj daje mniejsze, bardziej elastyczne programy. Jest to również najlepsza droga do tego świętego Graala, jakim jest reużywalność. Język jest z definicji reużywalny. Im więcej swojej aplikacji możesz przesunąć w dół do języka do pisania tego typu aplikacji, tym więcej Twojego oprogramowania będzie reużywalne.

Jakoś idea reużywalności została powiązana z programowaniem obiektowym w latach 80., i żadna ilość dowodów przeciwnych nie jest w stanie jej od tego uwolnić. Ale chociaż niektóre oprogramowanie obiektowe jest reużywalne, to co czyni je reużywalnym, to jego „od dołu do góry”, a nie jego „obiektowość”. Rozważmy biblioteki: są reużywalne, ponieważ są językiem, niezależnie od tego, czy są napisane w stylu obiektowym, czy nie.

Nie przewiduję zresztą zagłady programowania obiektowego. Chociaż nie sądzę, aby miało ono wiele do zaoferowania dobrym programistom, poza pewnymi wyspecjalizowanymi domenami, jest nieodparte dla dużych organizacji. Programowanie obiektowe oferuje zrównoważony sposób pisania spaghetti code. Pozwala na akreowanie programów jako serii poprawek. Duże organizacje zawsze rozwijają oprogramowanie w ten sposób i spodziewam się, że będzie to tak samo prawdziwe za sto lat, jak dzisiaj.

Skoro już mówimy o przyszłości, lepiej porozmawiać o obliczeniach równoległych, ponieważ to tam wydaje się tkwić ta idea. To znaczy, niezależnie od tego, kiedy o tym mówisz, obliczenia równoległe wydają się czymś, co ma nadejść w przyszłości.

Czy przyszłość kiedykolwiek dogoni to? Ludzie mówią o obliczeniach równoległych jako o czymś nieuchronnym od co najmniej 20 lat, a do tej pory nie miało to większego wpływu na praktykę programowania. A może miało? Już projektanci chipów muszą o tym myśleć, podobnie jak osoby próbujące pisać oprogramowanie systemowe na komputerach wieloprocesorowych.

Prawdziwe pytanie brzmi, jak wysoko po drabinie abstrakcji sięgnie równoległość? Za sto lat wpłynie to nawet na programistów aplikacji? Czy będzie to coś, o czym myślą piszący kompilatory, ale co jest zazwyczaj niewidoczne w kodzie źródłowym aplikacji?

Jedną z rzeczy, które wydają się prawdopodobne, jest to, że większość możliwości równoległości zostanie zmarnowana. Jest to szczególny przypadek mojego bardziej ogólnego przewidywania, że większość dodatkowej mocy obliczeniowej, którą otrzymamy, zostanie zmarnowana. Spodziewam się, że podobnie jak w przypadku zdumiewającej szybkości podstawowego sprzętu, równoległość będzie czymś dostępnym, jeśli poprosisz o to jawnie, ale zazwyczaj nie będzie używana. Oznacza to, że rodzaj równoległości, jaki będziemy mieli za sto lat, nie będzie, z wyjątkiem specjalnych zastosowań, masową równoległością. Spodziewam się, że dla zwykłych programistów będzie to bardziej jak możliwość tworzenia procesów, które wszystkie będą działać równolegle.

A to, podobnie jak proszenie o konkretne implementacje struktur danych, będzie czymś, co robi się dość późno w cyklu życia programu, podczas próby jego optymalizacji. Wersje 1 zazwyczaj będą ignorować wszelkie korzyści płynące z obliczeń równoległych, tak jak będą ignorować korzyści płynące z konkretnych reprezentacji danych.

Z wyjątkiem specjalnych rodzajów aplikacji, równoległość nie przeniknie programów pisanych za sto lat. Byłaby to przedwczesna optymalizacja, gdyby tak było.

Ile języków programowania będzie za sto lat? Wydaje się, że ostatnio pojawiło się ogromna liczba nowych języków programowania. Częściowo wynika to z tego, że szybszy sprzęt pozwolił programistom na różne kompromisy między szybkością a wygodą, w zależności od zastosowania. Jeśli jest to prawdziwy trend, sprzęt, który będziemy mieć za sto lat, powinien go tylko zwiększyć.

A jednak za sto lat może istnieć tylko kilka szeroko stosowanych języków. Częściowo dlatego mówię tak z optymizmu: wydaje się, że jeśli wykonasz naprawdę dobrą robotę, możesz stworzyć język, który będzie idealny do pisania wolnej wersji 1, a jednocześnie dzięki odpowiednim wskazówkom optymalizacyjnym dla kompilatora, zapewni również bardzo szybki kod, gdy będzie to konieczne. Ponieważ jestem optymistą, przewiduję, że pomimo ogromnej luki między akceptowalną a maksymalną wydajnością, programiści za sto lat będą mieli języki, które będą w stanie pokryć większość tej luki.

W miarę poszerzania się tej luki, profilery staną się coraz ważniejsze. Obecnie niewiele uwagi poświęca się profilowaniu. Wielu ludzi nadal wydaje się wierzyć, że sposób na uzyskanie szybkich aplikacji polega na pisaniu kompilatorów generujących szybki kod. W miarę poszerzania się luki między akceptowalną a maksymalną wydajnością, coraz jaśniejsze będzie, że sposób na uzyskanie szybkich aplikacji polega na posiadaniu dobrego przewodnika od jednego do drugiego.

Kiedy mówię, że może istnieć tylko kilka języków, nie włączam specyficznych dla dziedziny „małych języków”. Uważam, że takie osadzone języki to świetny pomysł i spodziewam się, że będą się one mnożyć. Ale spodziewam się, że będą one napisane jako wystarczająco cienkie skórki, aby użytkownicy mogli dostrzec pod spodem język ogólnego przeznaczenia.

Kto zaprojektuje języki przyszłości? Jednym z najbardziej ekscytujących trendów w ciągu ostatnich dziesięciu lat był rozwój języków open-source, takich jak Perl, Python i Ruby. Projektowanie języków przejmują hakerzy. Dotychczasowe wyniki są niechlujne, ale zachęcające. W Perlu, na przykład, istnieją oszałamiająco nowatorskie pomysły. Wiele z nich jest oszałamiająco złych, ale tak zawsze jest w przypadku ambitnych przedsięwzięć. Przy obecnym tempie mutacji, Bóg wie, w co Perl może ewoluować za sto lat.

Nie jest prawdą, że ci, którzy nie potrafią, uczą (niektórzy z najlepszych hakerów, jakich znam, to profesorowie), ale prawdą jest, że jest wiele rzeczy, których ci, którzy uczą, nie potrafią. Badania narzucają ograniczające kasty. W każdej dziedzinie akademickiej istnieją tematy, którymi można się zajmować, i inne, którymi nie można. Niestety, rozróżnienie między akceptowalnymi a zakazanymi tematami opiera się zazwyczaj na tym, jak intelektualnie brzmi praca, gdy jest opisywana w artykułach naukowych, a nie na tym, jak ważna jest dla uzyskania dobrych wyników. Ekstremalnym przypadkiem jest prawdopodobnie literatura; ludzie studiujący literaturę rzadko mówią coś, co byłoby choćby odrobinę przydatne dla tych, którzy ją tworzą.

Chociaż sytuacja jest lepsza w naukach ścisłych, nakład między pracą, którą można wykonywać, a pracą, która przynosi dobre języki, jest niepokojąco mały. (Olin Shivers elokwentnie narzekał na to.) Na przykład typy wydają się być niewyczerpanym źródłem artykułów naukowych, mimo że statyczne typowanie wydaje się wykluczać prawdziwe makra – bez których, moim zdaniem, żaden język nie jest wart używania.

Trend zmierza nie tylko w kierunku rozwoju języków jako projektów open-source, a nie „badań”, ale także w kierunku projektowania języków przez programistów aplikacji, którzy muszą ich używać, a nie przez piszących kompilatory. Wydaje się to dobrym trendem i spodziewam się, że będzie się on utrzymywał.

W przeciwieństwie do fizyki za sto lat, której przewidzenie jest prawie niemożliwe, myślę, że w zasadzie możliwe jest zaprojektowanie języka, który spodoba się użytkownikom za sto lat.

Jednym ze sposobów projektowania języka jest po prostu zapisanie programu, który chciałbyś móc napisać, niezależnie od tego, czy istnieje kompilator, który może go przetłumaczyć, czy sprzęt, który może go uruchomić. Robiąc to, możesz założyć nieograniczone zasoby. Wydaje się, że powinniśmy być w stanie wyobrazić sobie nieograniczone zasoby równie dobrze dzisiaj, jak za sto lat.

Jaki program chciałbyś napisać? Cokolwiek wymaga najmniej pracy. Z wyjątkiem tego, że nie do końca: cokolwiek wymagałoby najmniej pracy, gdyby Twoje pomysły na programowanie nie były już pod wpływem języków, do których jesteś przyzwyczajony. Taka wpływ może być tak wszechobecny, że przezwyciężenie go wymaga wielkiego wysiłku. Można by pomyśleć, że byłoby to oczywiste dla tak leniwych stworzeń jak my, jak wyrazić program z najmniejszym wysiłkiem. W rzeczywistości nasze pomysły na to, co jest możliwe, są zazwyczaj tak ograniczone przez język, w którym myślimy, że łatwiejsze sformułowania programów wydają się bardzo zaskakujące. Są to rzeczy, które trzeba odkryć, a nie coś, w co naturalnie się zapadamy.

Jedną z pomocnych sztuczek jest użycie długości programu jako przybliżenia ilości pracy potrzebnej do jego napisania. Nie długości w znakach, oczywiście, ale długości w odrębnych elementach składniowych – zasadniczo rozmiaru drzewa parsowania. Może nie być do końca prawdą, że najkrótszy program jest najmniej pracochłonny, ale jest na tyle blisko, że lepiej jest celować w solidny cel zwięzłości niż w mglisty, pobliski cel najmniejszej pracy. Wtedy algorytm projektowania języka staje się: spójrz na program i zapytaj, czy jest jakiś sposób, aby napisać go krócej?

W praktyce pisanie programów w wyimaginowanym stuletnim języku będzie działać w różnym stopniu, w zależności od tego, jak blisko jesteś rdzenia. Rutyny sortowania można napisać teraz. Ale trudno byłoby przewidzieć teraz, jakie rodzaje bibliotek mogą być potrzebne za sto lat. Prawdopodobnie wiele bibliotek będzie dla domen, które jeszcze nie istnieją. Jeśli SETI@home zadziała, na przykład, będziemy potrzebować bibliotek do komunikacji z obcymi. Chyba że oczywiście są na tyle zaawansowani, że już komunikują się w XML.

Na drugim krańcu, myślę, że można by zaprojektować podstawowy język już dziś. W rzeczywistości niektórzy mogliby argumentować, że został on już w większości zaprojektowany w 1958 roku.

Gdyby stuletni język był dostępny dzisiaj, czy chcielibyśmy w nim programować? Jednym ze sposobów odpowiedzi na to pytanie jest spojrzenie wstecz. Gdyby dzisiejsze języki programowania były dostępne w 1960 roku, czy ktokolwiek chciałby z nich korzystać?

Pod pewnymi względami odpowiedź brzmi: nie. Dzisiejsze języki zakładają infrastrukturę, która nie istniała w 1960 roku. Na przykład język, w którym wcięcia mają znaczenie, jak Python, nie działałby zbyt dobrze na terminalach drukujących. Ale pomijając takie problemy – zakładając na przykład, że wszystkie programy byłyby pisane na papierze – czy programiści z lat 60. lubiliby pisać programy w językach, których używamy teraz?

Myślę, że tak. Niektórzy z mniej wyobraźnych, którzy mieli artefakty wczesnych języków wbudowane w swoje wyobrażenia o tym, czym jest program, mogliby mieć problemy. (Jak można manipulować danymi bez arytmetyki wskaźników? Jak można zaimplementować schematy blokowe bez goto?) Ale myślę, że najmądrzejsi programiści poradziliby sobie bez problemu z wykorzystaniem dzisiejszych języków, gdyby je mieli.

Gdybyśmy mieli teraz stuletni język, służyłby on przynajmniej jako świetny pseudokod. A co z używaniem go do pisania oprogramowania? Ponieważ stuletni język będzie musiał generować szybki kod dla niektórych zastosowań, prawdopodobnie mógłby generować kod wystarczająco wydajny, aby działał akceptowalnie dobrze na naszym sprzęcie. Być może będziemy musieli udzielić więcej wskazówek optymalizacyjnych niż użytkownicy za sto lat, ale nadal może to być zysk netto.

Teraz mamy dwie idee, które po połączeniu sugerują interesujące możliwości: (1) stuletni język mógłby, w zasadzie, zostać zaprojektowany dzisiaj, i (2) taki język, gdyby istniał, mógłby być dobry do programowania dzisiaj. Kiedy widzisz te idee przedstawione w ten sposób, trudno nie pomyśleć: dlaczego nie spróbować napisać stuletniego języka teraz?

Kiedy pracujesz nad projektowaniem języka, myślę, że dobrze jest mieć taki cel i świadomie o nim pamiętać. Kiedy uczysz się jeździć, jedną z zasad, których Cię uczą, jest wyrównanie samochodu nie przez wyrównanie maski z pasami namalowanymi na drodze, ale przez celowanie w jakiś punkt w oddali. Nawet jeśli zależy Ci tylko na tym, co dzieje się w ciągu najbliższych dziesięciu stóp, jest to właściwa odpowiedź. Myślę, że możemy i powinniśmy robić to samo z językami programowania.

Przypisy

Uważam, że Lisp Machine Lisp był pierwszym językiem, który ucieleśniał zasadę, że deklaracje (z wyjątkiem zmiennych dynamicznych) były jedynie wskazówkami optymalizacyjnymi i nie zmieniały znaczenia poprawnego programu. Common Lisp wydaje się być pierwszym, który to wyraźnie stwierdził.

Podziękowania dla Trevora Blackwell, Roberta Morrisa i Dana Giffina za przeczytanie wersji roboczych tego tekstu, oraz dla Guido van Rossuma, Jeremy'ego Hyltona i reszty ekipy Pythona za zaproszenie mnie do wystąpienia na PyCon.