Diversity w polskim IT
Rafał Kotusiewicz
Rafał KotusiewiczCo-Owner @ nexonIT

Dlaczego jednak warto nauczyć się Lispu?

Sprawdź, w czym Lisp przebija Javę i Pythona i dowiedz się, kiedy będzie dla Ciebie najlepszym wyborem.
28.02.202010 min
Dlaczego jednak warto nauczyć się Lispu?

Jeśli zastanawiasz się, dlaczego od czasu do czasu ktoś wmawia Ci, że język, który powstał ponad pół wieku temu (właściwie to ponad 60 lat), wciąż wart jest nauczenia, to koniecznie to przeczytaj.

Czym jest Lisp

Lisp to z jednej strony język programowania (właściwie cała rodzina języków), a z drugiej, nieśmiertelna koncepcja wywodząca się z matematyki. Koncepcja, o której mowa, została opisana przez Alonzo Churcha pod koniec lat dwudziestych XX w., a sam język - i jego pierwsza implementacja - pojawiły się 30 lat później w 1958. Autorem projektu języka jest John McCarthy, a pierwszej implementacji Steve Russel. Obaj panowie (mistrz i uczeń) to postaci bardzo zasłużone na polu badań nad sztuczną inteligencją (zajmowali się tym, gdy rodzice większości z nas wciąż byli nastolatkami). Hipsterzy! :)

Alonzo Church, John McCarthy, Steve Russel
Źródła: Wikipedia, Independent, Wikipedia

Więcej o historii napisał Michał Herda w tekście Czym jest Lisp, więc nie ma sensu tego powtarzać. Jeśli nie czytałeś/aś tamtego tekstu, to bardzo mocno zachęcam Cię to tego, zanim zaczniesz czytać dalej moje przemyślenia i obserwacje. Teraz pójdę w nieco inną stronę i wyjaśnię, dlaczego dziś wciąż warto nauczyć się Lispu (w sensie ogólnym) i jak konkretne implementacje wspierają pewne zadania.

Konwencjonalne języki programowania

Granice mojego języka wyznaczają granice mojego świata. - L. Wittgenstein

Wittgenstein był filozofem, który zajmował się językiem i jego wpływem na ograniczenia naszego postrzegania świata (to bardzo ogólne podsumowanie, ale na potrzeby artykułu - wystarczające (zachęcam do zapoznania się z jego pracami) i jak trafnie podsumował tym jednym zdaniem, rozumiemy to, co potrafimy nazwać i opisać.

Jak to odnosi się do programowania, języków programowania? Zacznijmy od odpowiedzenia na pytanie, dlaczego języki programowania są takie, jakie są?

Czym Perl różni się od Javy?

Perl powstał, żeby rozwiązywać problemy, z którym borykali się administratorzy systemów (dawno temu). Miał być lepszą wersją systemowego shella. Miał być bardziej ekspresywny w tym  konkretnym kontekście, czy inaczej mówiąc „przypadku użycia".

Czy to się udało? Oczywiście. Język udostępnia bardzo łatwe metody odczytu plików, przekierowywanie strumieni itd... wszystko, czego potrzeba administratorowi, w nim jest. Łatwe do użycia. Tak było. W poprzednim dziesięcioleciu postanowiono zrobić z Perla prawdziwy język programowania do wszelkich zastosowań i moim zdaniem  trochę go to „popsuło". Perl w sposób naturalny wspomaga przetwarzanie  tekstu, bo w swoich codziennych kontekstach tym się właśnie zajmują jego użytkownicy.

Dlaczego Python kojarzy się z wcięciami?

Teraz przyjrzyjmy się Pythonowi. Jego twórca - Guido van Rossum - był wykładowcą uniwersyteckim, zapewne świetnym programistą, który zdawał sobie sprawę, że w programowaniu ogromną zaletą jest dobry styl.

Zarówno w życiu, jak i muzyce chodzi przede wszystkim o styl. - M. Davis

Zaprojektował swój język w taki sposób, aby wymuszał pewien styl formatowania tekstu programu. Struktura kodu wyznaczana jest poprzez wcięcia. W innych aspektach Python trochę przypomina C lub C++ (Perla odrobinę). Stał się więc dobrym narzędziem do nauczania programowania. Jest nim zresztą do dziś. Wystarczy spojrzeć ile z książek/kursów dla początkujących poświęconych jest Pythonowi i poleca go jako pierwszy język. I znowu - kontekst  narzucił formę.

Java

Trzeci przypadek to Java. Język, który powstał, by programować w oderwaniu od platformy docelowej (write once run everywhere), narzucił dość uniwersalny system prostych typów oraz łatwy w użyciu system obiektowy. To wciąż język, który z założenia miał być łatwy dla programistów używających C++ (czy jest dla nich zachęcający - to już sprawa dyskusyjna). System typów zapewniający bezpieczeństwo programów, łatwa składnia, dostęp do bibliotek etc. Znowu - kontekst narzuca formę.

Wnioski

W każdym z powyższych przypadków (i zapewniam Cię, że jest tak niemal z każdym językiem programowania) język powstał po to, by w prosty sposób rozwiązywać powtarzające się (a więc dobrze znane) problemy i do rozwiązywania dokładnie tych problemów jest dobrze przystosowany. Ale Wittgenstein to był nie lada myśliciel i jego spostrzeżenie jest trafne także dla nas - programistów. Co z tego wynika? Co najmniej dwie rzeczy:

  • granice możliwości (wygodnego) rozwiązywania problemów wyznacza używany przez nas język (nie da się napisać bootloadera w Javie czy Perlu)
  • warto znać kilka języków programowania, bo zwiększa to zdecydowanie wachlarz problemów, z którymi możemy skutecznie się mierzyć; trudno jest rozwiązywać różne problemy tym samym językiem


Jeśli w rękach masz tylko młotek, to każdy Twój problem staje się gwoździem.

Dlaczego Lisp jest inny?

Lisp to proste, a jednocześnie potężne narzędzie - coś pomiędzy młotem Thora

a czarodziejską różdżką Harry'ego Pottera

Lisp jest właściwie pozbawiony składni, a to, czym się posługujemy, używając go, to po prostu notacja. Programując w Lispie, tworzymy drzewo składni. Trochę działamy jak kompilator przekształcający, np. javowy kod w pierwszej fazie kompilacji. Ale żeby było ciekawie, możemy w trakcie prawdziwej kompilacji (właściwie to podczas wczytywania kodu do kompilatora) nieco to drzewo przekształcać. Innymi słowy, mamy dostępne makra, ale prawdziwe, bo programowalne całą potęgą języka. W żadnym wypadku nie jest to proste podstawianie łańcuchów jak np. w preprocesorze języków C czy C++. Brzmi dziwnie... albo cudownie… albo jednak dziwnie. Każdy odczyta ten krótki opis po swojemu. Ale dlaczego to takie ekscytujące? Jak ma Ci to pomóc w rozwiązywaniu codziennych problemów?


Lisp isn't a language, it's a building material
. - Alan Key

Programując w Lispie, najpierw tworzymy język (pamiętaj słowa Wittgensteina), który opisuje problem, przed którym stoimy. Nie... nie ma się co martwić, nie musimy za każdym razem od nowa wymyślać obsługi plików, sieci itd.

If you want to make an apple pie from scratch, you must first create the universe.  - Carl Sagan

Paul Graham nazywa to programowaniem od dołu do góry (ang. bottom up). Oczywiście, ma to swoje zalety, a dla programistów przyzwyczajonych do języków obiektowych, także i wady. Ale dobrzy programiści działają tak, pisząc także w Javie czy innym „nowoczesnym" języku. Wrócimy do tego za chwilę.

Zalety:

  • zanim zaczniesz pisać program i projektować wysokopoziomowe interfejsy, dobrze zrozumiesz problem na niskim poziomie
  • od samego początku pracy rozwiązujesz najmniejszy problem, jaki dostrzegasz
  • tworzysz język bardzo specyficzny dla dziedziny problemu (ang. domain specific language)


Jeśli jesteś czarnym charakterem kina Hollywood, to kończysz na czymś takim:

(conquer! 'world)

Po czym poznać, że warto sięgnąć po Lisp?

Jeśli pracujesz nad czymś zupełnie nowym, ale w takim prawdziwym sensie. Jeśli problem jest tak nietypowy, że nikt wcześniej się z nim nie zetknął, to stajesz w sytuacji, w której nie ma narzędzi, które służą do rozwiązywania takiego typu problemów. Przykłady? Najprostszy.. jakie dziś mamy klucze? Płaskie, oczkowe, nasadowe i pewnie jeszcze jakieś ich wersje. Czy 500 lat temu mieliśmy choćby jeden z tych rodzajów? Nie, bo nie było śrub, które by trzeba wkręcać lub wykręcać. Ktoś, kto wymyślił śruby, musiał także wymyślić klucze. Zachęcam Cię do poszukania większej liczby przykładów (oby lepszych niż mój) w ramach intelektualnej rozrywki. Wracamy do świata programowania i dzisiejszych problemów.

Masz przed sobą problem, którego nikt jeszcze nie dostrzegł (przy okazji, polecam lekturę książki „Zero to One" Petera Thiela). To jest właśnie problem wart rozwiązania. Szukasz właściwych narzędzi, żeby problem opisać i trafiasz na ścianę. Peter Thiel twierdzi, że to są prawdziwe problemy warte rozwiązania (przy okazji pewnie zostaniesz miliarderem, bo bycie po prostu milionerem nie jest już cool).

Okazuje się, że takich narzędzi nie ma. Musisz je stworzyć. Możesz wykorzystać swój ulubiony język (np. Javę) i tworzyć bibliotekę, klasy itd. Jeśli podejdziesz do tego właściwie, to stworzysz coś, co nazywa się DSL’em (wspominałem parę akapitów wcześniej) - język dziedzinowy, który mieszasz z tym, w którym ten język został napisany. Nie jest to nic nowego. Rozejrzyj się wokół:

  • Ruby/Rails -> ActiveRecord
  • Scala -> Akka
  • Groovy -> Gradle
  • Java -> JPQL, HQL


Przykłady można mnożyć. Efektem zwykle jest hybryda,

która z jednej strony wygląda adekwatnie do problemu, a z drugiej osadzona jest w języku, który wygląda, jak wygląda - zwykle dość konwencjonalnie.

Sięgamy po (jakiś) Lisp

Możesz oczywiście sięgnąć po mój ulubiony język, który zawsze wygląda tak samo. Kilka przykładów:

(with-open-file   ;; dość standardowa operacja 
  (foo "no-such-file" :if-does-not-exist nil)
  (read foo))

Twój DSL będzie wyglądał i działał podobnie:

(using-transaction :with :READ-COMMITED
 (execute-query "INSERT INTO table VALUES(?,?,?)"
   :with-params '(x y z))
 (execute-query "UPDATE table SET x=? WHERE y=?"
   :with-params '(x y z)))


Patrząc na ten kod, od razu dostrzegasz, o co chodzi, bo nazwy funkcji i słowa, których używasz, są tak mocno związane z dziedziną (tutaj SQL), że nie pozostawiają pola na żadne domysły czy wątpliwości. Składnia (tutaj notacja) jest naturalna dla języka, nie ma więc  problemów z czytelnością, czy rozumieniem przez niezaawansowanych programistów. Gdy dobrze się zastanowimy, biblioteka standardowa Lispa (Lispów w ogólności) to właśnie takie dziedzinowe DSL i do dziś najlepsze biblioteki powstają w taki sposób. Poniżej kilka przykładów w Clojure (najpopularniejszy dziś Lisp, działający na JVM):

SQLKorma

(select users
  (where (or (= :username "chris")
             (= :email "[email protected]"))))
;; executes: SELECT * FROM users WHERE (users.username = 'chris' OR users.email = '[email protected]')

(select users
  (where {:username [like "chris"]
          :status "active"
          :location [not= nil]}))
;; executes SELECT * FROM users WHERE (users.username LIKE 'chris' AND users.status = 'active' AND users.location IS NOT NULL)

Midje

(deftest migration
  (testing "Migration produces a new left and right map"
    (is (= {:new-left {} :clashes #{} :new-right {:a 1}}
           (migrate {:a 1} :a {}))))
  (testing "multiple keys can be moved at once"
    (is (= {:new-left {} :clashes #{} :new-right {:a 1 :b 2}}
           (migrate {:a 1, :b 2} :a :b {})))))

Jaki Lisp dziś?

Lisp to dość wiekowa koncepcja i przez lata pojawiał się w różnych formach i implementacjach. Najpopularniejsze z nich to:

- Common Lisp (mnóstwo implementacji),
- Scheme

ale powstało także wiele nowych:

- Racket (Scheme na sterydach)
- Clojure (bardzo popularny Lisp @ jvm)
- ClojureScript (clojure @ js)
- LFE (Lisp Flavoured Erlang)

Który z nich wybrać? Czym się różnią? Różnice występują na kilku poziomach:

- Lisp-1 vs. Lisp-2 różnice w traktowaniu przestrzeni nazwa dla "zmiennych" i funkcji
- niektóre implementacji wprowadzają elementy składni (Clojure wprowadził literały dla wektorów, hashmap itd)
- dodatkowe elementy składni bądź biblioteki standardowej w zależności od docelowej platformy (Clojure działa na JVM, ClojureScript korzysta z JavaScriptu, LFE to nie do końca Lisp i dostrzegalny w nim jest wpływy platformy EVM lub BEAM)

Wybierając swój Lisp, kieruj się potrzebami i środowiskiem, w którym pracujesz, platformą, na której udostępniasz programy lub po prostu smakiem. Czego ja używam? Najczęściej jest to Clojure/ClojureScript lub CommonLisp (SBCL/CLisp). Rzadziej sięgam po Racket (ale sięgam, LFE używam wciąż tylko eksperymentalnie). Paul Graham w swoim tweecie z 2016 roku zasugerował, żeby korzystać z Clojure’a.

Warto go posłuchać - w końcu napisał dwie książki o Lispie, własną implementację Lispu (arc) i w latach 90. napisał pierwszą prawdziwą web aplikację, korzystając właśnie z Lispa. Zakładam więc, że wie, co mówi.

Kto dziś używa Lispa (lub jakiegoś jego dialektu)?

- Cognitec, firma stojąca za Clojure
- ThoughtWorks
- Fablo (dziś Retailic)
- Walmart (ulubiony sklep Jarka Kuźniara)
- buzzlers.com - moja firma
- scalac.io - nie mam stuprocentowej pewności, ale jakiś czas temu firma poszukiwała specjalisty/specjalistów Clojure
- nudelta

…i wiele innych. Wciąż są firmy używające Common Lispa. Wystarczy poszukać.

Jakie książki?

Oczywiście, na rynku dostępnych jest mnóstwo książek, opisujących większość z tych implementacji (wydaje mi się, że LFE jeszcze nie doczekał się żadnej publikacji, ale to kwestia czasu). Jak ciekawostkę dodam, że kilka tygodni temu dzięki pomocy Adama - mojego przyjaciela - dotarła do mnie książka G. Steele’a „Common Lisp. The Language. 2nd ed” wydana - bagatela! - w 1990 roku. I jest do dziś dość aktualna.

Inna z książek o Lispie - “Practical Common Lisp” P. Seibela, wydana kilka lat temu, przez kilka tygodni była jedną z najlepiej sprzedających się książek o programowaniu na Amazonie.

CommonLisp:

- Guy Steele Jr.  „Common Lisp: The Language”
- P. Seibel „Practical Common Lisp”
- Edmund Weitz „Common Lisp Recipes”
- P. Norvig „Paradigms of Artificial Intelligence Programming”
- P. Graham „ANSI Common Lisp”

Clojure:

- Carin Meier „Living Clojure”
- A. Miller, S. Halloway, A. Bedra „Programming Clojure”
- A. Rathore „Clojure in Action”
- M. Fogus, C. Houser „Joy of Clojure”
- https://www.braveclojure.com/

Książek jest więcej, Lispów także.

- clojure.org
- racket-lang.org
- sbcl.org
- lfe.io

Zapowiada się długa podróż :)

Podsumowując…

Nakreśliłem nieco kontekst, ale dlaczego właściwie warto nauczyć się Lispu? Powodów jest co najmniej kilka:

  1. to świetny język, który pozwala w elegancki sposób rozwiązać większość problemów (bootloadera ciągle nie napiszemy)
  2. język dostosuje się do Ciebie i do problemu, który masz do rozwiązania - nie musisz się dostosowywać do języka i patrzeć przez jego pryzmat na problem
  3. w zależności od wybranej implementacji, mamy do dyspozycji wiele bardzo wiele, albo bardzo bardzo wiele dodatkowych bibliotek
  4. możesz używać Emacsa (tak naprawdę to zawsze możesz)
  5. rozwinie Cię jako programistę (lub programistkę) nawet, jeśli nie będziesz używać go w codziennej pracy


Podziękowania


Przy okazji tego tekstu podziękowania przesyłam Tomkowi, który nie miał nic przeciwko temu, żeby część ważnego systemu przepisać w Clojure, a na pomysł, by kolejną aplikację napisać całkowicie w CommonLispie, zareagował konkretnym: “Ok. Pasuje!” Dzięki za odwagę i zaufanie!

<p>Loading...</p>