← początek

Lisp czy Scheme

20 września 2025

Od kiedy ćwierć wieku temu przeczytałem w „Pragmatycznym Programiście” poradę (czy raczej zalecenie, zachętę), by co roku uczyć się nowego języka programowania, staram się to robić. Rzadko język przydaje mi się bezpośrednio w pracy czy poza nią, ale bardzo często poszerza moje horyzonty, uczy mnie czegoś nowego, daje inne spojrzenie na to, jak można rozwiązywać problemy za pomocą kodu. No i jest to po prostu przyjemne.

Co prawda w tym roku już uczyłem się nowego języka, Elixira, ale jakoś się nie polubiliśmy. Niby wszystko ma takie, jak trzeba, ale coś mi w nim nie pasuje. Kiedyś do niego wrócę, być może trafi się okazja napisania projektu, który wykorzysta jego mocne strony. Tej jesieni jednak chciałbym zgłębić jakiś nowy (dla mnie) Lisp.

Nie jestem zupełnym świeżakiem, jeśli chodzi o Lispy. Lubię Racket i całkiem sprawnie posługuję się Clojure. Początkowo myślałem, żeby może zagłębić się w sekrety tego pierwszego, ale jednak chciałbym nauczyć się czegoś nowego.

I tu pojawia się problem klęski urodzaju. Co wybrać, na czym się skupić?

Na wstępie odrzuciłem ClojureScript. Po pierwsze dlatego, że stosunkowo niewiele różni się od Clojure, a po drugie, że byłaby to bardziej nauka programowania webowego niż samo poznawanie języka. Więc nie.

Chciałbym nauczyć się języka, który nie jest zupełnie niszowy (tak, wiem, wszystkie Lispy są w mniejszym lub większym stopniu niszowe). Więc Jank, Janet czy LFE też odpadają.

(LFE jest ciekawy, ale to także raczej inne podejście do ekosystemu Erlanga niż nauka języka. Kiedyś go spróbuję, ale jeszcze nie teraz.)

Ostatecznie na shortliście znalazły się Common Lisp oraz Scheme… ale tu znowuż nie jest łatwo, bo implementacji Scheme jest wiele i różnią się między sobą. Z długiej listy wybrałem sobie Gerbil, Gambit i Guile.

Common Lisp

Język-legenda, o którym krążą w sieci niesamowite opowieści. Kilka lat temu liznąłem go pobieżnie, ale nie pociągnąłem tego dalej. Już od pewnego czasu mam ochotę do niego wrócić.

Common Lisp kompiluje się do kodu natywnego (chociaż nie musi) i jest ponoć dość szybki. Intryguje mnie, że specyfikacja języka została ustalona w 1994 roku i od tej pory nie było potrzeby jej zmieniać czy rewidować. Język jest na tyle elastyczny, że w ramach istniejących w nim mechanizmów można zaimplementować dowolne nowe rzeczy.

Z jednej strony jest „nudny”, bo nie ma żadnych modnych, nowych rzeczy, ale z drugiej strony jest wciąż żywy, używany, z zapleczem w postaci bardzo oddanych użytkowników. To jest intrygujące.

Gambit

Jedna z najstarszych wciąż rozwijanych wersji Scheme. Gambit oferuje kompilację do natywnych binarek (poprzez C) oraz do JavaScriptu (node). Przyznam, że nie rozumiem, jak Racket jest kompilowany (dokumentacja mówi i o bytekodzie, i o komilacji natywnej, i o kompilacji JIT, ale wszystko dzieje się jakoś automagicznie). Ciekawi mnie, jak w tej kwestii działa Gambit. Chwali się szybkością, a także lekkimi wątkami, które pozwalają na tworzenie skalowalnych serwerów. Wnioskuję z tego, że nie polega na wątkach systemowych, ale ma własny scheduler. Czy działa tak samo, kompilowany do C i Node? Czy to prosta abstrakcja zbudowana na asynchronicznym IO? Nie wiem, ale ciekawi mnie to.

Gerbil

Gerbil jest zbudowany na bazie Gambit, i tak samo kompiluje się do kodu natywnego. Oferuje system obiektowy (MetaObject Protocol) inspirowany Common Lispem, a także korutyny (coroutines) i aktorów komunikujących się za pomocą eventów.

Gerbil wydaje się rozszerzać Scheme w bardzo ciekawy sposób. Agentów można grupować w ensembles, czyli grupy aktorów działających na rozproszonych, luźno zdefiniowanych klastrach. Ciekawi mnie, jakie możliwości oferuje, jeśli chodzi o zapewnianie niezawodności w takim środowisku.

Guile

Guile powstał jako język do osadzania w aplikacjach, dający im możliwość rozszerzania o własne skrypty, ale teraz pozwala także pisania samodzielnych aplikacji. Ma własną maszynę wirtualną z kompilacją do natywnego kodu (JIT wykorzystujący GNU Lightning) i system obiektowy (tak, inspirowany Common Lispem). Ciekawe jest też, że Guile ma bibliotekę implementującą fibers (czyli ultralekkie wątki) za pomocą kontynuacji.

Do Guile przyciąga mnie to, że na bazie tej implementacji Scheme powstało kilka ciekawych projektów. Jeden z nich to Guix, manager zależności, innym jest biblioteka implementująca fibers za pomocą kontynuacji. Spritely także wybrało Guile jako jedną z dwóch docelowych platform (obok Racket), wypuszczając m.in. Hoot, czyli kompilator Scheme do WebAssembly. Zakładam, że to nie były przypadkowe decyzje.

Decyzja

Największym problemem przy wyborze jest fakt, że nie mam żadnego konkretnego projektu na myśli, więc nie mogę dobrać narzędzia, żeby dobrze się sprawdziło. Interesują mnie języki jako takie, a nie poprzez konkretne zastosowanie. Bez zagłębiania się w nie mogę też ocenić dokumentacji. Czy jest wystarczająca, żeby nauczyć rzeczy specyficznych dla języka, wyjaśnić zawiłe kwestie, odpowiedzieć na pytania i wątpliwości?

Któraś z implementacji Scheme wydaje się najprostsza: mają wspólną bazę, którą już z grubsza znam, musiałbym zatem przede wszystkim nauczyć się tego, co jest charakterystyczne dla wybranego wariantu. Jednak – który wybrać?

A jednak najbardziej kusi mnie Common Lisp. Jest dla mnie nowy, zaspokoi potrzebę nowości. Po drugie, ma bogaty ekosystem. Niewielkie jest niebezpieczeństwo, że będę musiał sam sobie napisać jakąś bibliotekę, której mi potrzeba. CL ma odium języka ostatecznego, szczytowego osiągnięcia – jestem ciekawy, w jakim stopniu jest to prawda w roku 2025.

A zatem Common Lisp. Zobaczymy, jak go odbiorę.