W Internecie bardzo łatwo znaleźć artykuły opisujące metafizyczną wyższość Lispa nad pomniejszymi (czytaj: wszystkimi innymi) językami programowania. Olśnienie, kompletna zmiana sposobu myślenia, cały świat wygląda nagle inaczej, iluminacja (tak, wiem, że to znaczy to samo co olśnienie), stary, po prostu totalny zen. Całkiem śmieszną parodię okołolispowej propagandy można znaleźć tutaj.

Smutna prawda jest jednak taka, że ludzie nie sięgają po nowy język programowania tylko po to, aby przeżyć olśnienie albo pogłębić swoje zrozumienie istnienia. Do tego służyć powinny raczej literatura, filozofia (polecam Quine'a i Wittgensteina), matematyka (twierdzenie Goedla), muzyka, w ostateczności narkotyki...
Nauczenia się zupełnie nowego języka programowania to jest zazwyczaj ogromny wysiłek. Bo przecież tu nie chodzi tylko o składnię (składnia lispowa, jak dziwna i nienaturalna by się wydawała na początku, po dwóch dniach staje się bardzo naturalna), ale również o pragmatykę nowego języka. Chodzi mi o rzeczy typu: jakich użyć bibliotek, jak je zainstalować, jak dostarczyć gotowa aplikację klientowi, co jest dobre, co jest złe, jakie pomysły są z góry skazane na porażkę, itp. Jeżeli ktoś w miarę normalny podejmuje ten ogromny wysiłek, zazwyczaj ma jakiś problem, który chce rozwiązać, i wierzy, że dzięki lispowi rozwiązanie tego problemu będzie lepsze, efektywniejsze, ciekawsze, a gotowy program łatwiejszy w pielęgnacji.
(Oczywiście, ciekawość poznawcza jest bardzo dobrym powodem, aby nauczyć się Lispa. Zazwyczaj jednak ciekawość poznawcza jest motywowana chęcią fajnego wykorzystania nowo nabytej wiedzy, a nie pragnieniem wiedzy samej dla siebie.)
Teraz, oczyściwszy przedpole, mogę opisać kilka powodów, dla których uważam, że Common Lisp jest lepszy od wielu innych, bardziej popularnych języków. Przez “lepszy”, oczywiście, rozumiem: lepiej dostosowany do moich potrzeb i pozwalający na łatwiejsze rozwiązanie sporej części moich problemów.
Makra
Makra lispowe są konstruktem językowym umożliwiającym metaprogramowanie. Czyli, narzędziem umożliwiającym pisanie programów, które same piszą inne programy. Brzmi to świetnie. Pytanie jednak: jak to się przekłada na praktykę? Czy jest to wygodne? Czy sprawia to, że programista może skupić się na rozwiązywanym problemie zamiast walczyć z systemem, czy wręcz przeciwnie, wysiłek związany ze stosowaniem makr niweczy wszystkie ich korzyści?
Makro definiuje się jak zwykłą funkcję. Różnica jest następująca: zamiast zwracać jakąś wartość, makro ma za zadanie zwrócić kod, którym kompilator zastąpi jego wywołanie w źródle. Istotną kwestią jest to, że generowanie owego kodu jest bardzo proste, dzięki “nieintuicyjnej“ składni Lispa. Mianowicie, program lispowy składa się z list, więc tylko listy musimy generować. (Kiedyś zastanawiałem się, jak można by dodać makra do Pythona i doszedłem do wniosku, że byłoby to dość skomplikowane. Bardzo lubię wyrażenia regularne, ale pisanie skomplikowanych programów za pomocą zastępowania ciągów znaków innymi ciągami znaków wydaje się dość nietrywialne. Nie wspomnę już o tym, jak bardzo grupowanie bloków kodu za pomocą tabulacji (coś, co bardzo lubię w Pythonie) utrudniałoby całą operację.)
Kiedy należy używać makr? Wtedy, gdy zauważamy, że nasze programy piszemy według jakiegoś wzorca, i chcielibyśmy ten wzorzec wyabstrahować i wykorzystać w innych miejscach (w końcu jedną z głównych zadań komputerów jest zastąpienie ludzi w prostych, algorytmizowalnych czynnościach --- dlaczego miałoby to nie dotyczyć programistów?). Niezły przykład takiego zastosowania makra można znaleźć tutaj.
W Common Lispie, w przeciwieństwie do Javy, C++ albo Pythona metody przynależą nie do klas, a do funkcji (tzw. generic functions). Z syntaktycznego punktu widzenia wywołanie takiej funkcji nie różni się niczym od wywołania dowolnej innej, zwykłej funkcji (nie ma kropek, strzałek ani żadnego innego śmiecia). Gdy taka funkcja zostanie wywołana, decyduje ona którą metodę zawołać na podstawie rodzaju wszystkich swoich argumentów. Jest to zachowanie inne niż w pozostałych językach, które wymieniłem powyżej, gdzie jedynie pierwszy argument (czyli np. w Pythonie self) decyduje o tym, która metodę wywołać.
MOP
Ostatnią wielką przewagą Lispa jest daleko posunięta introspekcja, osiągana dzięki Metaobject Protocol. Jest to jedna z najciekawszych funkcji Lispa, choć jej sensowne i poprawne użycie może być dość nietrywialne.
Każdy obiekt należy do jakiejś klasy. Pytanie: czym jest z kolei klasa? Otóż, w Common Lispie klasa również jest obiektem, typu standard-class. Nie tylko funkcje, ale też generic functions i metody są obiektami. Co to znaczy dla nas, programistów? Otóż, możemy zmieniać klasy w trakcie działania programu. Z tego co wiem, dla Javy istnieje biblioteka dająca podobne możliwości, ale w porównaniu z możliwościami MOP-a Lispowego jest dość ograniczona.
Idąc dalej, skoro klasy są (meta)obiektami typu standard-class (którą możemy nazywać metaklasą), nic nie stoi na przeszkodzie, aby napisać klasę dziedziczącą po standard-class i sprawić, aby definiowane przez nas obiekty były właśnie tego typu. Czyli, zdefiniować metaklasę. Do czego możemy to wykorzystać? Kilka pomysłów:
- Nie podoba się nam wielokrotne dziedziczenie? Proszę bardzo, napiszmy metaklasę obsługującą jedynie pojedyncze dziedziczenie!
- Obiekty, które działają jak komórki w arkuszu kalkulacyjnym, automatycznie zmieniając wartości w swoich polach w zależności od stanu innych obiektów. Z tego co wiem, jest to używane w aplikacjach służących do projektowania samolotów pasażerskich, gdzie np. wymiary różnych elementów zależą od wymiarów podzespołów wchodzących w ich skład.
- Obiekty z leniwym wartościowaniem pól (patrz lazy evaluation), których wartości powinny być wyliczone jedynie i nie wcześniej niż wtedy, kiedy są naprawdę potrzebne.
- Trwałe (persistent) obiekty, automagicznie zapisujące swoje wartości w bazie danych.
- ...
MOP przełamuje pewną barierę: programista, który go używa, jest prawie na tym samym poziomie, co projektant języka. Jak wiadomo, With great power comes great responsibility. Ale, chyba nie warto się tego bać: w najgorszym wypadku nasze programy przestaną działać (-;.
Oczywiście, może błądzę, a podobne rzeczy są lepiej rozwiązane w Foobarbaz (za Foobarbaz wstaw swój ulubiony język programowania różny od Common Lispa) --- będę bardzo wdzięczny za uświadomienie mnie w komentarzach. Co można, na przykład powiedzieć o makrach w Scheme. Oficjalnie są higieniczne, ale słyszałem, że większość ludzi używa Common Lispowych -- czy ktoś wie, czy to prawda? Tak samo, istnieje coś zwanego Tiny-CLOS --- jak bardzo to działa?