Zwięzłość to Moc

Maj 2002

| "Ilość znaczenia skompresowana w małej przestrzeni przez znaki algebraiczne jest kolejnym czynnikiem ułatwiającym rozumowanie, które jesteśmy przyzwyczajeni prowadzić przy ich pomocy."

- Charles Babbage, cytowany w wykładzie Iversona na temat nagrody Turinga

W dyskusji na temat problemów poruszonych w Revenge of the Nerds na liście mailingowej LL1, Paul Prescod napisał coś, co utkwiło mi w pamięci.

Celem Pythona jest regularność i czytelność, a nie zwięzłość.

Na pierwszy rzut oka wydaje się to dość dewastującym stwierdzeniem na temat języka programowania. O ile mi wiadomo, zwięzłość = moc. Jeśli tak, to podstawiając, otrzymujemy:

Celem Pythona jest regularność i czytelność, a nie moc.

i to nie wydaje się kompromisem (jeśli jest kompromisem), na który chciałbyś się zdecydować. To już blisko stwierdzenia, że celem Pythona nie jest bycie skutecznym językiem programowania.

Czy zwięzłość = moc? Wydaje mi się to ważnym pytaniem, być może najważniejszym pytaniem dla każdego, kto interesuje się projektowaniem języków, i takim, które warto byłoby bezpośrednio rozważyć. Nie jestem jeszcze pewien, czy odpowiedź brzmi proste tak, ale wydaje się to dobrą hipotezą na początek.

Hipoteza

Moją hipotezą jest, że zwięzłość jest mocą, lub jest na tyle blisko, że z wyjątkiem patologicznych przykładów można je traktować jako tożsame.

Wydaje mi się, że zwięzłość jest tym, do czego służą języki programowania. Komputery zadowoliłyby się tym, że są instruowane bezpośrednio w języku maszynowym. Myślę, że głównym powodem, dla którego podejmujemy trud opracowywania języków wysokiego poziomu, jest uzyskanie przewagi, dzięki czemu możemy powiedzieć (a co ważniejsze, pomyśleć) w 10 liniach języka wysokiego poziomu to, co wymagałoby 1000 linii języka maszynowego. Innymi słowy, głównym celem języków wysokiego poziomu jest zmniejszenie rozmiaru kodu źródłowego.

Jeśli mniejszy kod źródłowy jest celem języków wysokiego poziomu, a moc czegoś jest tym, jak dobrze osiąga swój cel, to miarą mocy języka programowania jest to, jak małe czyni on nasze programy.

I odwrotnie, język, który nie zmniejsza naszych programów, wykonuje kiepską robotę w tym, co powinny robić języki programowania, jak nóż, który słabo kroi, lub druk, który jest nieczytelny.

Metryki

Ale małe w jakim sensie? Najczęstszą miarą rozmiaru kodu są linie kodu. Ale myślę, że ta metryka jest najczęstsza, ponieważ jest najłatwiejsza do zmierzenia. Nie sądzę, żeby ktokolwiek naprawdę wierzył, że jest to prawdziwy test długości programu. Różne języki mają różne konwencje dotyczące tego, ile powinno się umieszczać na linii; w C wiele linii nie zawiera nic poza separatorem lub dwoma.

Innym łatwym testem jest liczba znaków w programie, ale to też nie jest zbyt dobre; niektóre języki (na przykład Perl) używają po prostu krótszych identyfikatorów niż inne.

Myślę, że lepszą miarą rozmiaru programu byłaby liczba elementów, gdzie elementem jest wszystko, co byłoby odrębnym węzłem, gdyby narysować drzewo reprezentujące kod źródłowy. Nazwa zmiennej lub funkcji jest elementem; liczba całkowita lub zmiennoprzecinkowa jest elementem; fragment tekstu literałowego jest elementem; element wzorca lub dyrektywa formatująca jest elementem; nowy blok jest elementem. Istnieją przypadki graniczne (czy -5 to dwa elementy czy jeden?), ale myślę, że większość z nich jest taka sama dla każdego języka, więc nie wpływają one zbytnio na porównania.

Ta metryka wymaga dopracowania i może wymagać interpretacji w przypadku konkretnych języków, ale myślę, że próbuje ona zmierzyć właściwą rzecz, czyli liczbę części, które ma program. Myślę, że drzewo, które narysowalibyśmy w tym ćwiczeniu, jest tym, co musisz stworzyć w swojej głowie, aby pojąć program, a zatem jego rozmiar jest proporcjonalny do ilości pracy, którą musisz wykonać, aby go napisać lub przeczytać.

Projektowanie

Ten rodzaj metryki pozwoliłby nam porównywać różne języki, ale to nie jest, przynajmniej dla mnie, jego główna wartość. Główną wartością testu zwięzłości jest jako przewodnik w projektowaniu języków. Najbardziej użytecznym porównaniem między językami jest porównanie dwóch potencjalnych wariantów tego samego języka. Co mogę zrobić w języku, aby programy były krótsze?

Jeśli obciążenie koncepcyjne programu jest proporcjonalne do jego złożoności, a dany programista może tolerować stałe obciążenie koncepcyjne, to jest to to samo, co pytanie, co mogę zrobić, aby umożliwić programistom jak najwięcej wykonania? A to wydaje mi się tożsame z pytaniem, jak zaprojektować dobry język?

(Nawiasem mówiąc, nic nie czyni bardziej oczywistym, że stara śpiewka "wszystkie języki są równoważne" jest fałszywa, niż projektowanie języków. Kiedy projektujesz nowy język, ciągle porównujesz dwa języki – język, jeśli zrobiłbym x, i jeśli nie zrobiłbym – aby zdecydować, który jest lepszy. Gdyby to było naprawdę bezsensowne pytanie, równie dobrze można by rzucić monetą.)

Dążenie do zwięzłości wydaje się dobrym sposobem na znalezienie nowych pomysłów. Jeśli możesz zrobić coś, co skraca wiele różnych programów, to prawdopodobnie nie jest to przypadek: prawdopodobnie odkryłeś użyteczną nową abstrakcję. Możesz nawet napisać program, który pomoże, przeszukując kod źródłowy w poszukiwaniu powtarzających się wzorców. Wśród innych języków, te z reputacją zwięzłości byłyby tymi, na które warto zwrócić uwagę w poszukiwaniu nowych pomysłów: Forth, Joy, Icon.

Porównanie

Pierwszą osobą, która pisała o tych kwestiach, o ile wiem, był Fred Brooks w książce The Mythical Man Month. Pisał, że programiści generowali mniej więcej taką samą ilość kodu dziennie, niezależnie od języka. Kiedy pierwszy raz przeczytałem to w wieku dwudziestu kilku lat, było to dla mnie wielkim zaskoczeniem i wydawało się mieć ogromne implikacje. Oznaczało to, że (a) jedynym sposobem na szybsze pisanie oprogramowania było użycie bardziej zwięzłego języka, i (b) ktoś, kto podjął trud zrobienia tego, mógł zostawić konkurentów, którzy tego nie zrobili, w tyle.

Hipoteza Brooksa, jeśli jest prawdziwa, wydaje się być w samym sercu hackingu. W latach od tego czasu uważnie przyglądałem się wszelkim dowodom, jakie mogłem uzyskać na to pytanie, od formalnych badań po anegdoty dotyczące poszczególnych projektów. Nie widziałem niczego, co by mu zaprzeczało.

Nie widziałem jeszcze dowodów, które wydawałyby mi się rozstrzygające, i nie spodziewam się ich. Badania takie jak porównanie języków programowania Lutz Prechelta, choć generują wyniki, których się spodziewałem, zazwyczaj wykorzystują problemy zbyt krótkie, aby były znaczącymi testami. Lepszym testem języka jest to, co dzieje się w programach, które pisze się miesiąc. A jedynym prawdziwym testem, jeśli wierzysz, jak ja, że głównym celem języka jest bycie dobrym do myślenia (a nie tylko do mówienia komputerowi, co robić, gdy już o tym pomyślisz), jest to, jakie nowe rzeczy możesz w nim napisać. Dlatego każde porównanie języków, w którym musisz spełnić z góry określoną specyfikację, testuje nieco niewłaściwą rzecz.

Prawdziwym testem języka jest to, jak dobrze potrafisz odkrywać i rozwiązywać nowe problemy, a nie to, jak dobrze potrafisz go używać do rozwiązania problemu, który ktoś inny już sformułował. Te dwa kryteria są dość różne. W sztuce media takie jak haft i mozaika działają dobrze, jeśli z góry wiesz, co chcesz stworzyć, ale są absolutnie kiepskie, jeśli nie. Kiedy chcesz odkryć obraz w trakcie jego tworzenia – jak to ma miejsce w przypadku wszystkiego tak złożonego jak obraz osoby, na przykład – musisz użyć bardziej płynnego medium, takiego jak ołówek, tusz lub farba olejna. I rzeczywiście, sposób, w jaki praktycznie wykonuje się gobeliny i mozaiki, polega na najpierw stworzeniu obrazu, a następnie jego skopiowaniu. (Słowo "karton" pierwotnie używano do opisania obrazu przeznaczonego do tego celu).

Oznacza to, że nigdy nie będziemy mieli dokładnych porównań względnej mocy języków programowania. Będziemy mieli precyzyjne porównania, ale nie dokładne. W szczególności jawne badania mające na celu porównanie języków, ponieważ prawdopodobnie będą wykorzystywać małe problemy i będą koniecznie wykorzystywać z góry określone problemy, będą miały tendencję do niedoszacowania mocy bardziej potężnych języków.

Raporty z pola, chociaż będą one z natury mniej precyzyjne niż badania "naukowe", prawdopodobnie będą bardziej znaczące. Na przykład Ulf Wiger z Ericsson przeprowadził badanie, które wykazało, że Erlang był 4-10 razy bardziej zwięzły niż C++, a proporcjonalnie szybszy w tworzeniu oprogramowania:

Porównania między projektami rozwojowymi wewnątrz Ericsson wskazują na podobną produktywność linii na godzinę, obejmującą wszystkie fazy rozwoju oprogramowania, w zasadzie niezależnie od użytego języka (Erlang, PLEX, C, C++ lub Java). Tym, co różnicuje różne języki, staje się zatem objętość kodu źródłowego.

Badanie to dotyczy również bezpośrednio kwestii, która była tylko dorozumiana w książce Brooksa (ponieważ mierzył on linie debugowanego kodu): programy napisane w bardziej potężnych językach mają tendencję do posiadania mniejszej liczby błędów. Staje się to celem samym w sobie, być może ważniejszym niż produktywność programisty, w zastosowaniach takich jak przełączniki sieciowe.

Test smaku

Ostatecznie myślę, że musisz zaufać swojej intuicji. Jak się czuje programowanie w tym języku? Myślę, że najlepszym sposobem na znalezienie (lub zaprojektowanie) najlepszego języka jest stanie się nadwrażliwym na to, jak dobrze język pozwala ci myśleć, a następnie wybór/projektowanie języka, który wydaje się najlepszy. Jeśli jakaś funkcja języka jest niezgrabna lub ograniczająca, nie martw się, będziesz o tym wiedzieć.

Taka nadwrażliwość będzie miała swoją cenę. Okazuje się, że nie możesz znosić programowania w niezgrabnych językach. Uważam, że programowanie w językach bez makr jest nieznośnie ograniczające, tak jak dla kogoś przyzwyczajonego do dynamicznego typowania nieznośnie ograniczające jest powrót do programowania w języku, w którym trzeba deklarować typ każdej zmiennej i nie można tworzyć listy obiektów różnych typów.

Nie jestem jedyny. Znam wielu hakerów Lisp, którym się to przytrafiło. W rzeczywistości najdokładniejszą miarą względnej mocy języków programowania może być procent ludzi znających język, którzy podejmą się każdej pracy, w której będą mogli używać tego języka, niezależnie od dziedziny zastosowania.

Ograniczenia

Myślę, że większość hakerów wie, co to znaczy, gdy język wydaje się ograniczający. Co się dzieje, gdy czujesz to? Myślę, że to to samo uczucie, gdy ulica, którą chcesz przejechać, jest zablokowana i musisz wybrać długi objazd, aby dotrzeć tam, gdzie chciałeś. Jest coś, co chcesz powiedzieć, a język ci na to nie pozwala.

Myślę, że tak naprawdę chodzi o to, że ograniczający język to taki, który nie jest wystarczająco zwięzły. Problem polega nie tylko na tym, że nie możesz powiedzieć tego, co zaplanowałeś. Chodzi o to, że objazd, który narzuca ci język, jest dłuższy. Wypróbuj ten eksperyment myślowy. Załóżmy, że jest jakiś program, który chciałeś napisać, a język nie pozwolił ci go wyrazić tak, jak zaplanowałeś, ale zamiast tego zmusił cię do napisania programu w inny sposób, który był krótszy. Dla mnie przynajmniej nie byłoby to zbyt ograniczające. Byłoby to jak zablokowana ulica, którą chciałeś przejechać, a policjant na skrzyżowaniu kieruje cię na skrót zamiast na objazd. Świetnie!

Myślę, że większość (dziewięćdziesiąt procent?) poczucia ograniczenia wynika z konieczności napisania programu w języku dłuższym niż ten, który masz w głowie. Ograniczenie to głównie brak zwięzłości. Więc kiedy język wydaje się ograniczający, oznacza to (głównie), że nie jest wystarczająco zwięzły, a kiedy język nie jest zwięzły, będzie wydawał się ograniczający.

Czytelność

Cytat, od którego zacząłem, wspomina o dwóch innych cechach: regularności i czytelności. Nie jestem pewien, czym jest regularność, ani jaką przewagę, jeśli w ogóle, ma kod, który jest regularny i czytelny, nad kodem, który jest jedynie czytelny. Ale myślę, że wiem, co oznacza czytelność, i myślę, że jest ona również związana ze zwięzłością.

Musimy tu uważać, aby odróżnić czytelność pojedynczej linii kodu od czytelności całego programu. To drugie ma znaczenie. Zgadzam się, że linia Basic jest prawdopodobnie bardziej czytelna niż linia Lisp. Ale program napisany w Basic będzie miał więcej linii niż ten sam program napisany w Lisp (zwłaszcza gdy przekroczysz próg Greenspunland). Całkowity wysiłek związany z czytaniem programu w Basic z pewnością będzie większy.

całkowity wysiłek = wysiłek na linię x liczba linii

Nie jestem tak pewien, że czytelność jest bezpośrednio proporcjonalna do zwięzłości, jak jestem pewien, że moc jest, ale z pewnością zwięzłość jest czynnikiem (w sensie matematycznym; patrz powyższe równanie) w czytelności. Więc może nawet nie mieć sensu mówienie, że celem języka jest czytelność, a nie zwięzłość; mogłoby to być jak mówienie, że celem jest czytelność, a nie czytelność.

To, co oznacza czytelność na linię, dla użytkownika napotykającego język po raz pierwszy, to to, że kod źródłowy będzie wyglądał niegroźnie. Więc czytelność na linię może być dobrą decyzją marketingową, nawet jeśli jest złą decyzją projektową. Jest to izomorficzne do bardzo skutecznej techniki pozwalania ludziom na płacenie w ratach: zamiast przerażać ich wysoką ceną początkową, mówisz im o niskiej miesięcznej płatności. Plany ratalne są jednak dla kupującego stratą netto, podobnie jak zwykła czytelność na linię prawdopodobnie jest dla programisty. Kupujący dokona wielu tych niskich, niskich płatności; a programista będzie czytał wiele tych indywidualnie czytelnych linii.

Ten kompromis poprzedza języki programowania. Jeśli jesteś przyzwyczajony do czytania powieści i artykułów prasowych, twoje pierwsze doświadczenie z czytaniem pracy matematycznej może być zniechęcające. Przeczytanie jednej strony może zająć pół godziny. A jednak jestem całkiem pewien, że problemem nie jest notacja, chociaż tak może się wydawać. Praca matematyczna jest trudna do przeczytania, ponieważ idee są trudne. Gdybyś te same idee wyraził prozą (jak musieli to robić matematycy, zanim wyewoluowali zwięzłe notacje), nie byłyby one wcale łatwiejsze do przeczytania, ponieważ praca rozrosłaby się do rozmiaru książki.

W jakim stopniu?

Kilka osób odrzuciło ideę, że zwięzłość = moc. Myślę, że bardziej użyteczne byłoby, zamiast po prostu argumentować, że są one takie same lub nie, zapytać: w jakim stopniu zwięzłość = moc? Ponieważ zwięzłość jest oczywiście dużą częścią tego, do czego służą języki wyższego poziomu. Jeśli nie do tego tylko służą, to do czego jeszcze służą i jak ważne są te inne funkcje w porównaniu?

Nie proponuję tego tylko po to, aby uczynić debatę bardziej cywilizowaną. Naprawdę chcę znać odpowiedź. Kiedy, jeśli w ogóle, język jest zbyt zwięzły dla własnego dobra?

Hipoteza, od której zacząłem, brzmiała, że z wyjątkiem patologicznych przykładów, zwięzłość można uznać za tożsamą z mocą. Miałem na myśli, że w każdym języku, który ktoś zaprojektowałby, byłyby one tożsame, ale że jeśli ktoś chciałby zaprojektować język wyraźnie w celu obalenia tej hipotezy, prawdopodobnie mógłby to zrobić. Właściwie nie jestem nawet tego pewien.

Języki, nie programy

Powinniśmy jasno powiedzieć, że mówimy o zwięzłości języków, a nie pojedynczych programów. Z pewnością możliwe jest napisanie pojedynczych programów zbyt gęsto.

Pisałem o tym w On Lisp. Złożony makro może musieć zaoszczędzić wiele razy więcej niż własna długość, aby był uzasadniony. Jeśli napisanie jakiegoś skomplikowanego makra mogłoby zaoszczędzić dziesięć linii kodu za każdym razem, gdy go używasz, a samo makro ma dziesięć linii kodu, to uzyskujesz oszczędność netto linii, jeśli użyjesz go więcej niż raz. Ale to nadal może być zły ruch, ponieważ definicje makr są trudniejsze do odczytania niż zwykły kod. Możesz musieć użyć makra dziesięć lub dwadzieścia razy, zanim przyniesie ono netto poprawę czytelności.

Jestem pewien, że każdy język ma takie kompromisy (chociaż podejrzewam, że stawka rośnie wraz z mocą języka). Każdy programista musiał widzieć kod, który jakiś sprytny człowiek uczynił marginalnie krótszym, używając wątpliwych sztuczek programistycznych.

Więc nie ma o tym dyskusji – przynajmniej nie ze strony mnie. Pojedyncze programy z pewnością mogą być zbyt zwięzłe dla własnego dobra. Pytanie brzmi, czy język może być? Czy język może zmuszać programistów do pisania kodu, który jest krótki (w elementach) kosztem ogólnej czytelności?

Jednym z powodów, dla których trudno sobie wyobrazić, że język może być zbyt zwięzły, jest to, że gdyby istniał jakiś nadmiernie zwarty sposób wyrażenia czegoś, prawdopodobnie istniałby również dłuższy sposób. Na przykład, gdybyś uważał, że programy Lisp używające wielu makr lub funkcji wyższego rzędu są zbyt gęste, mógłbyś, jeśli wolisz, napisać kod izomorficzny do Pascala. Jeśli nie chcesz wyrazić silni w Arc jako wywołanie funkcji wyższego rzędu (rec zero 1 * 1-) możesz również napisać rekurencyjną definicję: (rfn fact (x) (if (zero x) 1 (* x (fact (1- x))))) Chociaż nie mogę sobie przypomnieć żadnych przykładów z pamięci, interesuje mnie pytanie, czy język może być zbyt zwięzły. Czy istnieją języki, które zmuszają cię do pisania kodu w sposób pokrętny i niezrozumiały? Jeśli ktoś ma przykłady, byłbym bardzo zainteresowany ich zobaczeniem.

(Przypomnienie: Szukam programów, które są bardzo gęste według metryki "elementów" nakreślonej powyżej, a nie tylko programów, które są krótkie, ponieważ można pominąć separatory, a wszystko ma jednoliterową nazwę.)