Pięć pytań o projektowanie języków
Maj 2001
(To są notatki, które zrobiłem na potrzeby dyskusji panelowej na temat projektowania języków programowania w MIT 10 maja 2001 r.)
1. Języki programowania są dla ludzi.
Języki programowania to sposób, w jaki ludzie rozmawiają z komputerami. Komputerowi byłoby wszystko jedno, w jakim języku by mówił, byleby był jednoznaczny. Powodem, dla którego mamy języki wysokiego poziomu, jest to, że ludzie nie radzą sobie z językiem maszynowym. Celem języków programowania jest zapobieganie przeciążeniu naszych biednych, kruchych ludzkich mózgów masą szczegółów.
Architekci wiedzą, że niektóre rodzaje problemów projektowych są bardziej osobiste niż inne. Jednym z najczystszych, najbardziej abstrakcyjnych problemów projektowych jest projektowanie mostów. Tam twoje zadanie polega głównie na pokonaniu danej odległości przy użyciu najmniejszej ilości materiału. Drugi skrajny przypadek to projektowanie krzeseł. Projektanci krzeseł muszą spędzać czas, myśląc o ludzkich tyłkach.
Oprogramowanie różni się w ten sam sposób. Projektowanie algorytmów do routingu danych przez sieć jest miłym, abstrakcyjnym problemem, podobnym do projektowania mostów. Natomiast projektowanie języków programowania jest jak projektowanie krzeseł: chodzi o radzenie sobie z ludzkimi słabościami.
Większość z nas niechętnie się do tego przyznaje. Projektowanie systemów o wielkiej matematycznej elegancji brzmi dla większości z nas znacznie bardziej atrakcyjnie niż uleganie ludzkim słabościom. I jest miejsce na matematyczną elegancję: niektóre rodzaje elegancji sprawiają, że programy są łatwiejsze do zrozumienia. Ale elegancja nie jest celem samym w sobie.
A kiedy mówię, że języki muszą być projektowane tak, aby odpowiadały ludzkim słabościom, nie mam na myśli, że języki muszą być projektowane dla złych programistów. W rzeczywistości uważam, że powinieneś projektować dla najlepszych programistów, ale nawet najlepsi programiści mają ograniczenia. Nie sądzę, żeby ktokolwiek chciałby programować w języku, w którym wszystkie zmienne to litera x z indeksami całkowitymi.
2. Projektuj dla siebie i swoich przyjaciół.
Jeśli spojrzysz na historię języków programowania, wiele z najlepszych z nich było językami zaprojektowanymi dla ich autorów do użytku, a wiele z najgorszych zostało zaprojektowanych dla innych ludzi.
Gdy języki są projektowane dla innych ludzi, zawsze jest to konkretna grupa innych ludzi: ludzi nie tak mądrych jak projektant języka. Dostajesz więc język, który mówi do ciebie protekcjonalnie. Cobol jest najbardziej ekstremalnym przypadkiem, ale wiele języków jest przesiąkniętych tym duchem.
Nie ma to nic wspólnego z tym, jak abstrakcyjny jest język. C jest dość niskopoziomowy, ale został zaprojektowany do użytku przez jego autorów, i dlatego hakerzy go lubią.
Argumentem za projektowaniem języków dla złych programistów jest to, że jest więcej złych programistów niż dobrych. To może być prawda. Ale ci nieliczni dobrzy programiści piszą nieproporcjonalnie duży procent oprogramowania.
Interesuje mnie pytanie, jak zaprojektować język, który spodoba się najlepszym hakerom? Przypadkiem uważam, że to jest to samo pytanie, co jak zaprojektować dobry język programowania?, ale nawet jeśli tak nie jest, to jest to przynajmniej ciekawe pytanie.
3. Daj programiście jak najwięcej kontroli.
Wiele języków (zwłaszcza tych zaprojektowanych dla innych ludzi) ma postawę guwernantki: próbują cię powstrzymać przed robieniem rzeczy, które ich zdaniem nie są dla ciebie dobre. Lubię przeciwne podejście: daj programiście jak najwięcej kontroli.
Kiedy po raz pierwszy uczyłem się Lispa, najbardziej podobało mi się to, że traktował mnie jak równego partnera. W innych językach, których się wtedy nauczyłem, istniał język i istniał mój program, napisany w tym języku, i te dwa były bardzo oddzielne. Ale w Lispie funkcje i makra, które pisałem, były takie same jak te, które tworzyły sam język. Mogłem przepisać język, jeśli chciałem. Miało to taki sam urok jak oprogramowanie open-source.
4. Dąż do zwięzłości.
Zwięzłość jest niedoceniana, a nawet pogardzana. Ale jeśli spojrzysz w serca hakerów, zobaczysz, że naprawdę ją kochają. Ile razy słyszałeś, jak hakerzy z nostalgią mówią o tym, jak w, powiedzmy, APL, mogli zrobić niesamowite rzeczy za pomocą kilku linii kodu? Myślę, że wszystko, co naprawdę mądrzy ludzie naprawdę kochają, jest warte uwagi.
Myślę, że prawie wszystko, co można zrobić, aby programy były krótsze, jest dobre. Powinno być dużo funkcji bibliotecznych; wszystko, co może być domyślne, powinno być; składnia powinna być rażąco zwięzła; nawet nazwy rzeczy powinny być krótkie.
I nie tylko programy powinny być krótkie. Instrukcja obsługi również powinna być cienka. Dobra część instrukcji obsługi jest poświęcona wyjaśnieniom, zastrzeżeniom i ostrzeżeniom oraz przypadkom specjalnym. Jeśli zmusisz się do skrócenia instrukcji obsługi, w najlepszym przypadku zrobisz to, naprawiając rzeczy w języku, które wymagały tak wielu wyjaśnień.
5. Przyznaj, czym jest hakowanie.
Wielu ludzi chciałoby, aby hakowanie było matematyką, a przynajmniej czymś w rodzaju nauki przyrodniczej. Myślę, że hakowanie jest bardziej jak architektura. Architektura jest powiązana z fizyką, w tym sensie, że architekci muszą projektować budynki, które się nie zawalą, ale faktycznym celem architektów jest tworzenie wspaniałych budynków, a nie dokonywanie odkryć dotyczących statyki.
Hakerzy lubią tworzyć wspaniałe programy. I myślę, że przynajmniej w naszych umysłach musimy pamiętać, że pisanie wspaniałych programów jest godnym podziwu zajęciem, nawet jeśli ta praca nie przekłada się łatwo na konwencjonalną walutę intelektualną artykułów badawczych. Intelektualnie, projektowanie języka, który programiści pokochają, jest równie wartościowe, jak projektowanie okropnego języka, który ucieleśnia jakąś ideę, o której można opublikować artykuł.
1. Jak organizować duże biblioteki?
Biblioteki stają się coraz ważniejszym elementem języków programowania. Stają się też coraz większe, a to może być niebezpieczne. Jeśli znalezienie funkcji bibliotecznej, która zrobi to, czego chcesz, zajmuje więcej czasu niż napisanie jej samemu, to cały ten kod nic nie robi, poza tym, że sprawia, że twoja instrukcja obsługi jest gruba. (Instrukcje Symbolics były tego przykładem.) Myślę więc, że będziemy musieli pracować nad sposobami organizacji bibliotek. Ideałem byłoby zaprojektowanie ich w taki sposób, aby programista mógł zgadnąć, jakie wywołanie biblioteczne zrobi właściwą rzecz.
2. Czy ludzie naprawdę boją się składni prefiksowej?
To jest otwarty problem w tym sensie, że zastanawiałem się nad tym od lat i nadal nie znam odpowiedzi. Składnia prefiksowa wydaje mi się całkowicie naturalna, z wyjątkiem być może matematyki. Ale może być tak, że duża część niepopularności Lispa wynika po prostu z nieznanej składni. Czy należy coś z tym zrobić, jeśli to prawda, to już inne pytanie.
3. Czego potrzebujesz do oprogramowania serwerowego?
Myślę, że wiele z najbardziej ekscytujących nowych aplikacji, które zostaną napisane w ciągu najbliższych dwudziestu lat, to będą aplikacje oparte na sieci Web, co oznacza programy, które działają na serwerze i komunikują się z tobą za pośrednictwem przeglądarki internetowej. A do pisania tego typu programów możemy potrzebować kilku nowych rzeczy.
Jedną z rzeczy, których będziemy potrzebować, jest wsparcie dla nowego sposobu wydawania aplikacji serwerowych. Zamiast jednej lub dwóch dużych wersji rocznie, jak w przypadku oprogramowania desktopowego, aplikacje serwerowe są wydawane jako seria małych zmian. Można mieć nawet pięć lub dziesięć wydań dziennie. I z reguły wszyscy będą zawsze używać najnowszej wersji.
Wiesz, jak można projektować programy tak, aby można je było debugować? Cóż, oprogramowanie serwerowe również musi być projektowane tak, aby można je było zmieniać. Musisz być w stanie łatwo je zmieniać, lub przynajmniej wiedzieć, co jest małą zmianą, a co doniosłą.
Inną rzeczą, która może okazać się przydatna dla oprogramowania serwerowego, co zaskakujące, są kontynuacje. W oprogramowaniu opartym na sieci Web można użyć czegoś w rodzaju stylu przekazywania kontynuacji, aby uzyskać efekt podprogramów w z natury bezstanowym świecie sesji internetowej. Może warto byłoby mieć rzeczywiste kontynuacje, jeśli nie byłoby to zbyt kosztowne.
4. Jakie nowe abstrakcje pozostały do odkrycia?
Nie jestem pewien, na ile jest to rozsądna nadzieja, ale jedną z rzeczy, które osobiście bardzo bym chciał zrobić, jest odkrycie nowej abstrakcji – czegoś, co zrobiłoby taką różnicę, jak posiadanie funkcji pierwszej klasy, rekurencji, a nawet parametrów słów kluczowych. To może być niemożliwe marzenie. Te rzeczy nie są odkrywane zbyt często. Ale zawsze szukam.
1. Możesz używać dowolnego języka, który chcesz.
Pisanie programów aplikacyjnych kiedyś oznaczało pisanie oprogramowania desktopowego. A w oprogramowaniu desktopowym istnieje duża skłonność do pisania aplikacji w tym samym języku, co system operacyjny. I tak dziesięć lat temu pisanie oprogramowania w zasadzie oznaczało pisanie oprogramowania w C. Ostatecznie wyewoluowała tradycja: programy aplikacyjne nie mogą być pisane w nietypowych językach. I ta tradycja rozwijała się tak długo, że nauczyli się jej również ludzie nietechniczni, tacy jak menedżerowie i inwestorzy venture capital.
Oprogramowanie serwerowe burzy cały ten model. Dzięki oprogramowaniu serwerowemu możesz używać dowolnego języka, który chcesz. Prawie nikt jeszcze tego nie rozumie (zwłaszcza menedżerowie i inwestorzy venture capital). Kilku hakerów to rozumie, i dlatego słyszymy o nowych, niezależnych językach, takich jak Perl i Python. Nie słyszymy o Perlu i Pythonie dlatego, że ludzie używają ich do pisania aplikacji Windows.
Co to oznacza dla nas, jako ludzi zainteresowanych projektowaniem języków programowania, to to, że istnieje teraz potencjalnie rzeczywista publiczność dla naszej pracy.
2. Szybkość pochodzi z profilerów.
Projektanci języków, a przynajmniej implementatorzy języków, lubią pisać kompilatory, które generują szybki kod. Ale nie sądzę, żeby to właśnie sprawiało, że języki są szybkie dla użytkowników. Knuth dawno temu zauważył, że szybkość ma znaczenie tylko w kilku krytycznych wątkach. I każdy, kto tego próbował, wie, że nie można zgadnąć, gdzie są te wątki. Odpowiedzią są profilery.
Projektanci języków rozwiązują niewłaściwy problem. Użytkownicy nie potrzebują benchmarków, aby działać szybko. Potrzebują języka, który pokaże im, które części ich własnych programów wymagają przepisania. Stamtąd w praktyce bierze się szybkość. Więc może byłoby to ogólnie korzystne, gdyby implementatorzy języków poświęcili połowę czasu, który poświęciliby na optymalizację kompilatora, na napisanie dobrego profilera.
3. Potrzebujesz aplikacji, aby napędzać projekt języka.
To może nie być absolutna zasada, ale wydaje się, że wszystkie najlepsze języki ewoluowały razem z jakąś aplikacją, do której były używane. C został napisany przez ludzi, którzy potrzebowali go do programowania systemowego. Lisp był rozwijany częściowo do różniczkowania symbolicznego, a McCarthy był tak chętny do rozpoczęcia pracy, że pisał programy do różniczkowania nawet w pierwszym artykule o Lispie w 1960 roku.
Jest szczególnie dobrze, jeśli twoja aplikacja rozwiązuje jakiś nowy problem. To będzie skłaniać twój język do posiadania nowych funkcji, których potrzebują programiści. Osobiście interesuje mnie napisanie języka, który będzie dobry do pisania aplikacji serwerowych.
[Podczas panelu Guy Steele również poruszył ten punkt, z dodatkową sugestią, że aplikacja nie powinna polegać na pisaniu kompilatora dla twojego języka, chyba że twój język jest przeznaczony do pisania kompilatorów.]
4. Język musi być dobry do pisania programów jednorazowych.
Wiesz, czym jest program jednorazowy: coś, co piszesz szybko do jakiegoś ograniczonego zadania. Myślę, że gdybyś się rozejrzał, odkryłbyś, że wiele dużych, poważnych programów zaczęło się jako programy jednorazowe. Nie zdziwiłbym się, gdyby większość programów zaczęła się jako programy jednorazowe. A zatem, jeśli chcesz stworzyć język, który jest dobry do pisania oprogramowania w ogóle, musi być dobry do pisania programów jednorazowych, ponieważ jest to stadium larwalne większości oprogramowania.
5. Składnia jest powiązana z semantyką.
Tradycyjnie uważa się, że składnia i semantyka są całkowicie oddzielne. To może zabrzmieć szokująco, ale być może tak nie jest. Myślę, że to, czego chcesz w swoim języku, może być związane z tym, jak to wyrażasz.
Rozmawiałem ostatnio z Robertem Morrisem, a on zauważył, że przeciążanie operatorów jest większym plusem w językach ze składnią infiksową. W języku ze składnią prefiksową każda zdefiniowana przez ciebie funkcja jest efektywnie operatorem. Jeśli chcesz zdefiniować plus dla nowego typu liczby, który wymyśliłeś, możesz po prostu zdefiniować nową funkcję do ich dodawania. Jeśli zrobisz to w języku ze składnią infiksową, istnieje duża różnica w wyglądzie między użyciem przeciążonego operatora a wywołaniem funkcji.
1. Nowe języki programowania.
W latach 70. projektowanie nowych języków programowania było modne. Ostatnio nie jest. Ale myślę, że oprogramowanie serwerowe ponownie uczyni nowe języki modnymi. Dzięki oprogramowaniu serwerowemu możesz używać dowolnego języka, który chcesz, więc jeśli ktoś zaprojektuje język, który faktycznie wydaje się lepszy od innych dostępnych, znajdą się ludzie, którzy zaryzykują i go użyją.
2. Time-Sharing.
Richard Kelsey przedstawił to jako ideę, której czas nadszedł ponownie na ostatnim panelu, i całkowicie się z nim zgadzam. Moim zdaniem (i wydaje się, że Microsoftu) większość obliczeń przeniesie się z komputerów stacjonarnych na zdalne serwery. Innymi słowy, time-sharing powraca. I myślę, że potrzebne będzie wsparcie dla niego na poziomie języka. Na przykład, wiem, że Richard i Jonathan Rees wykonali dużo pracy nad implementacją planowania procesów w Scheme 48.
3. Wydajność.
Niedawno zaczęło się wydawać, że komputery są w końcu wystarczająco szybkie. Coraz częściej zaczęliśmy słyszeć o kodzie bajtowym, co sugeruje mi przynajmniej, że czujemy, że mamy zapas cykli. Ale nie sądzę, żebyśmy mieli, w przypadku oprogramowania serwerowego. Ktoś będzie musiał zapłacić za serwery, na których działa oprogramowanie, a liczba użytkowników, których mogą obsłużyć na maszynę, będzie dzielnikiem ich kosztów kapitałowych.
Myślę więc, że wydajność będzie miała znaczenie, przynajmniej w wąskich gardłach obliczeniowych. Szczególnie ważne będzie szybkie wykonywanie operacji wejścia/wyjścia, ponieważ aplikacje serwerowe wykonują dużo operacji wejścia/wyjścia.
Może się okazać, że kod bajtowy ostatecznie nie będzie opłacalny. Sun i Microsoft wydają się obecnie mierzyć w rodzaju bitwy kodów bajtowych. Ale robią to, ponieważ kod bajtowy jest wygodnym miejscem do wtrącenia się w proces, a nie dlatego, że kod bajtowy sam w sobie jest dobrym pomysłem. Może się okazać, że całe to pole bitwy zostanie ominięte. To byłoby dość zabawne.
1. Klienci.
To tylko przypuszczenie, ale moje przypuszczenie jest takie, że wygrywającym modelem dla większości aplikacji będzie model czysto serwerowy. Projektowanie oprogramowania, które działa przy założeniu, że każdy będzie miał twojego klienta, jest jak projektowanie społeczeństwa przy założeniu, że każdy będzie po prostu uczciwy. Byłoby to z pewnością wygodne, ale trzeba założyć, że nigdy się to nie zdarzy.
Myślę, że nastąpi proliferacja urządzeń, które będą miały jakiś rodzaj dostępu do sieci Web, a wszystko, co będziesz mógł o nich założyć, to to, że mogą obsługiwać prosty HTML i formularze. Czy będziesz miał przeglądarkę w swoim telefonie komórkowym? Czy będzie telefon w twoim palm pilot? Czy twój blackberry dostanie większy ekran? Czy będziesz mógł przeglądać sieć na swoim gameboyu? Twoim zegarku? Nie wiem. I nie muszę wiedzieć, jeśli postawię na to, że wszystko będzie tylko na serwerze. Jest to po prostu znacznie bardziej niezawodne, aby mieć wszystkie mózgi na serwerze.
2. Programowanie obiektowe.
Zdaję sobie sprawę, że jest to kontrowersyjne, ale nie sądzę, że programowanie obiektowe jest tak wielką sprawą. Myślę, że jest to dobry model dla pewnych rodzajów aplikacji, które potrzebują tego specyficznego rodzaju struktury danych, takich jak systemy okienkowe, symulacje i programy CAD. Ale nie widzę powodu, dla którego miałby to być model dla całego programowania.
Myślę, że częścią powodu, dla którego ludzie w dużych firmach lubią programowanie obiektowe, jest to, że generuje ono dużo tego, co wygląda na pracę. Coś, co można by naturalnie reprezentować jako, powiedzmy, listę liczb całkowitych, można teraz reprezentować jako klasę z wszelkiego rodzaju rusztowaniami, zamieszaniem i całym tym zgiełkiem.
Inną atrakcją programowania obiektowego jest to, że metody dają pewien efekt funkcji pierwszej klasy. Ale to stara wiadomość dla programistów Lispa. Kiedy masz rzeczywiste funkcje pierwszej klasy, możesz ich po prostu używać w dowolny sposób, który jest odpowiedni do danego zadania, zamiast wciskać wszystko w formę klas i metod.
Myślę, że oznacza to dla projektowania języków, że nie powinieneś zbyt głęboko wbudowywać programowania obiektowego. Może rozwiązaniem jest oferowanie bardziej ogólnych, podstawowych narzędzi, a następnie pozwolenie ludziom na projektowanie dowolnych systemów obiektowych, jakie chcą, jako bibliotek.
3. Projektowanie przez komitet.
Projektowanie języka przez komitet jest dużą pułapką, i to nie tylko z powodów, o których wszyscy wiedzą. Wszyscy wiedzą, że komitety zazwyczaj tworzą nieporadne, niespójne projekty. Ale myślę, że większym zagrożeniem jest to, że nie podejmą ryzyka. Kiedy jedna osoba jest odpowiedzialna, może podjąć ryzyko, na które komitet nigdy by się nie zgodził.
Czy jednak ryzyko jest konieczne do zaprojektowania dobrego języka? Wielu ludzi może podejrzewać, że projektowanie języka to coś, w czym należy trzymać się dość blisko konwencjonalnej mądrości. Zakładam, że to nieprawda. We wszystkim innym, co robią ludzie, nagroda jest proporcjonalna do ryzyka. Dlaczego projektowanie języka miałoby być inne?