Doświadczenia i praca z Clojure

0

Studia, studia i po, dlatego pomyślałem że w wyniku zwiększonej ilości czasu poszerzyłbym horyzonty.
Zastanawiałem się nad Kotlinem i choć wiem że staje się bardzo popularny to jakoś niespecjalnie widzę potrzebę jego nauki skoro to po prostu lepsza Java.
Wiele osób zachwala Scale i wiem że też dość łatwo trafić na projekty, ale coś mnie ciągnie w kierunku Clojure - jego składnia nie przypomina niczego co znam, a z tego co zdążyłem się dowiedzieć jest to świetny język do modelowania, rozwiązuje wiele problemów z współbieżnością i pewnie wiele innych.

W którymś numerze JavaMagazine był nawet przed scalą, ale jak to z nim jest? Ktoś pracował z tym komercyjnie? Słyszał o takich projektach?
No i jakie są wasze doświadczenia z tym językiem? Bo jestem ciekawy co myślicie :)

Pierwszą przeszkodą okazało się IDE, myślałem że Intellij ma odpowiednią wtyczkę, a okazuje się że jest dodatkowo płatna komercyjnie (nie używam niczego czego nie mogę potem użyć komercyjnie). Emacsa nie lubię, wtyczki do eclipse, netbeans, VS Code i vim wydają się martwe, a w zasadzie pozostają jakieś takie dziwne aplikacje typu NightCode. Może macie jakieś tipy? :D

0

Użyj cursive ten edytor bazuje na intelij. Nim zaczniesz pisac poznaj structural editing np. paredit wtedy nawiasy przestają męczyć.

0

Clojure to jest implementacja Common Lispa (choć to uproszczenie) i jest to całkiem fajna zabawka. Na produkcji w jednym projekcie wykorzystywaliśmy do obsługi STM i hardkorowej współbieżności.

Ciekawi mnie ta płatna wtyczka, bo z tego, co kojarzę, to w IntelliJ są co najmniej dwie.

2
Koziołek napisał(a):

Clojure to jest implementacja Common Lispa (choć to uproszczenie)

Lepiej powiedzieć, że jest dialektem Lispa, podobnie jak Common Lisp i Scheme. One wszystkie się od siebie różnią, choć sam faktem bycia Lispem jest znaczący. Łączy je potęga, głównie dzięki metaprogramowaniu. Jeśli ciągnie Cię, to próbuj, Lisp to oświecenie dla programisty, hehe. Jesli nauczysz się Clojure, inne nie powinny być problemem. :) Z drugiej strony Lisp nie jest łatwy. Jeśli się gdzieś zaklinujesz, polecam Scalę, mój drugi ulubiony język (po Common Lispie, wolę natywne implementacje). Do większości zadań wystarczy, dobrze wspiera programowanie funkcyjne (w odróżnieniu od Javy — proszę, nie wspominajcie o streamach, to jest żart, nie wsparcie). Tylko nie daje oświecenia. Za to jest łatwiejsza i bardzo podobna do Javy i często sie w niej korzysta z API javy, więc jednocześnie uczysz się Javy. Choć z Clojure może być podobnie. W końcu też chodzi na JVM.

Co do Vima, wcale nie jest martwy, to dobre narzędzie (też daje pewien rodzaj oświecenia :)). Nawet w javie koduję w vimie. Choć jeśli wolisz, jest coś takiego jak atom. Podobno podobne tylko nowocześniejsze trochę.

1

@orchowskia: Odnośnie vima, to póki co mam takie dosyć mocno prowizoryczne rozwiązanie, ale sprawdza się dobrze. Tu jest kod: https://github.com/BartekLew/workplace/tree/master/pipe-cl .
Jest kod w Common Lispie — to byś musiał sobie przetłumaczyć, ale to raczej jest prosty kod i on po prostu implementuje REPL, tylko że przez strumienie (przy okazji łapie wszystkie błędy)
Jest kod w bashu, który uruchamia ten program, przy okazji tworzy strumienie, common lisp kiepsko wspiera nieprzenośne rzeczy.
I wreszcie dwie linijki kodu dla vima. Pierwsze dowiązanie po prostu przechodzi o jeden poziom do góry w nawiasach, a drugi zaznacza całe bierzace nawiasy, wysyła do strumienia wejściowego i potem czyta z wyjściowego. Proste jak budowa cepa. Jest jedna niedogodność, że vim wysyła całe linijki, nawet jeśli zaznaczysz tylko część. Szukam obejścia.

Co do podpowiedzi, właśnie kończę pisać artykuł o Lispie. Tylko że o Common Lispie, więc szczegóły raczej się nie będą się zgadzać, ale przypuszczam, że idea podobna.

6

Mam chwilę czasu więc dam jeszcze jedna odpowiedź taka, która może trochę złagodzić twój start :)

1. Nawiasy

Clojure to LISP. Na początku nauki gdzie nie spojrzysz tam widać pełno nawiasów, które przez pierwszy miesiąc mocno utrudniają zapis kodu, a jeszcze mocniej odczyt kodu innych osób.

Na tym etapie najmocniej pomógł mi: paredit (wtedy kodowanie bardziej opiera się na stosowaniu wcięć, które automatycznie przesuwają nawiasy).

Oraz REPL w który ułatwił mi interpretację kodu innych osób. Po prostu z kodu wyciągałem podwyrażenia, wkładałem je do REPL i poprzez przekształcenia danych jakie zaobserwowałem szybciej łapałem co dana osoba miała na myśli.

Inna rzecz jaka jeszcze ułatwia spojrzenie na nawiasy to książka: SICP oraz Hackerzy i Malarze :D

2. Błędy

Druga rzecz jaka mnie niepokoiła to napotykane błędy. Nie są one oczywiste, często długie i czasem wymagają wiedzy z zakresu java/javascript. Nie będę ukrywał, ale na początku musisz być na to gotowy. To potrwa trochę nim zrozumiesz to co robisz źle :)

Rzecz jaka ułatwia przetrwanie to rozszerzenie które lepiej wyświetla traceback np. pyro oraz używaj REPL. Jak używasz REPL wówczas rzadziej popełnisz błąd, ponieważ twoje oprogramowanie nonstop ewoluuje. Jeśli popełnisz błąd łatwo jest namierzyć co źle zrobiłeś. Na tym etapie trochę ciężko jest mi wyjaśnić czym różni się to od zwykłego programowania. Programowanie kładzie nacisk na interakcję z programistą.

3. Wolne uruchamianie

Clojure wolno się odpala. Jezuu.. jak odpalam migrację to zdążę herbatę wstawić i wrócić nim migracja się skończy. :) 

Dlatego zmień flow pracy z programem. Nie odpalaj clojure nonstop od nowa. Odpal raz i działaj w nim nonstop. Podepnij REPL do edytora i pisz program, i w REPL odpalaj konkretne funkcje/testy/zadania.

4. REPL

Ostatni krok na etapie przygotowań to REPL. Przestaw się jak najmocniej możesz na programowanie w REPL. Poprzednie punkty to tylko początek. Im wczesniej to zrobisz to tak jakby zrozumiesz szybciej jakie możliwości ma Clojure na dalszych etapach.

Np. wyobraź sobie, że możesz odpalić REPL i programować w nim program tak jakby to był ogranizm. Tzn możesz przekształcać program, obserować pośrednie stany, wprowadzać nowe funkcje, przekształcać je na żywo. Ogólnie program zmienia się w środowisko w którym rzeźbisz swój docelowy system.

Jednocześnie wiele rzeczy może wtedy ulec zmianie, możesz swój język zmienić tak by podporządkować go temu systemowi. Dla mnie takie programowanie ma niesamowitą moc rażenia, a to tylko początek tego wszystkiego.

5. Biblioteki

Gdy zaczniesz pisać swój program pewnie będziesz chciał użyć frameworka, biblioteki cokolwiek co przyspieszy Twoją prace. Tutaj są pewne odmienne zdania w środowisku clojure.

Pierwsze to jest fakt, że nie ma frameworków, tzn są ale nie są one tak popularne i nie odgrywają tak ważnej roli jak np. railsy w ruby, czy django w pythonie. Poza tym frameworki w clojure to bardziej zbiór skonfigurowanych bibliotek, by start był prostszy i tyle. Do webówki warto poznać Luminus.

W społeczności clojure bardziej ceni się pisanie mniejszych bibliotek (ale nie tak małych jak w nodejs).

To co może zaskoczyć to fakt, że wiele bibliotek w clojure swój ostatni commit miało 2-4 lata temu i co ciekawe taka biblioteka może być jak najbardziej w porządku. Ona nie jest zapomniana. Ona po prostu nie sprawia problemów i nie jest w nieskończoność rozszerzana.

Druga sprawa jeśli nie masz biblioteki w clojure to dzięki interop możesz bez problemu użyć kodu z javy / javascript. Ja do tej pory używałem bibliotek do manipulacji pdf i grafiką i nie napotkałem większych problemów.

6. Orientacja na dane

Programowanie w Clojure wymaga trochę innej postawy niż w OOP. O ile w innych językach coraz większy nacisk kładzie się na pompowanie abstrakcje, na hermetyzację, dziedziczenie, polimorfizm, interfejsy itp to w przypadku clojure nie jest to dobra droga.

Jeśli myślisz o modelowaniu czegoś to używaj wbudowanych typów. Clojure jest zorientowany na dane. I tutaj najlepiej jest myśleć, że dane to dane. I jak coś modelujemy to z użyciem: mapy map, wectorów map, zbióry liczb itp Nie próbuj nadmiernie się ubezpieczać tak jak w java, inaczej tylko na tym stracisz.

Wiele jest przeciwników tego podejścia (szczególnie osoby, które potrzebują jawnych typów, statycznej kontroli itp) - niestety dla nich to podejście jest mega uciążliwe, śliskie i trudne do zakceptowania.

Wbrew pozorom używanie typów wbudowanych ma parę genialnych w prostocie rzeczy.

Jeśli korzystasz z REPL to nonstop widzisz swoje dane. Jak program się zmienia to widzisz jakie były przejścia. To rozwiązanie fajnie sprawdza się jak masz front SPA i piszesz coś pokroju react/redux, ale też spoko jest gdy robisz coś z danymi z bazy, albo gdy pracujesz z requestami / responsami. Weź pod uwagę, że jak masz niemutowalne dane to może je współdzielić na kilka sesji przez nrepl. Masz program i możesz zdalnie do niego się podpinać i nim sterować. Coś takiego w praktyce pozwala na podpięcie do przeglądarki i trzymanie w niej stanu, jeśli stronka się zmienia, stan jest pamiętany, a ty dodatkowo możesz operować na stanie z poziomu dodatkowego terminala i własnych pomocniczych funkcji.

Inna rzecz to sposób zapisu programów. Jeśli wiesz, że twoje programy są zbudowane z prostych typów to możesz pisać funkcje tak, by w sygnaturce od razu dokonywały destruction - takie odpakowanie danych, byś miał już nazwane zmienne, które pochodzą z wnętrz struktury. Dzięki temu funkcje stają się znacznie prostsze w odczycie i zapisie.

7. Programowanie funkcyjne

Pisząc funkcyjnie ogólnie piszemy wyrażenia, które na podstawie danych wejściowych produktują dane wyjściowe.

Ze wględu, że dane są niemodyfikowalne to by zrobić jakąś operację najczęściej musisz myśleć o programie jak o pipelinach. Robisz je składając różne funkcje. Tutaj dobrze jest opanować kompozycje funkcji, threading macros, ponieważ takie rzeczy mocno wpływają na czytelność takiego programu.

Blog z którego czerpałem dużo wskazówek odnośnie zapisu kodu funkcyjnego to: https://stuartsierra.com

8. Lewniwe sekwencje

Wraz z budowaniem pipelinów o jakich wspomniałem w poprzednim punkcie ważnym pojęciem są leniwe sekwencje. Chciałbym dodać, że są one przereklamowane i ułatwiają napisanie błędnego kodu. Warto je używać z rozwagą.

Najbardziej typowy błąd wynika z odroczenia ewaluacji. Np. jeśli masz funkcje która ma blok try/catch a w środku mieli dane z użyciem leniwej sekwencji to ten try/catch może nie zadziałać, leniwa sekwencja natualnie bez wykonywania kodu może opuścić kontekst funkcję i dopiero jak pojawi się potrzeba ewaluacji to może pojawić się błąd, którego już niestety nie przechwyci try/catch. Między innymi podobne rzeczy dzieją się jak robi się asynchroniczne operacje i tzn dynamiczny binding.

Podsumowanie

Dobra notka robi się trochę przydługa, ale dodam, że to bardziej początek. Jest więcej tematów takich jak
jak kodowanie współbieżne, asynchroniczne z core.async obsługa frontu, react + funkcyjne kodowanie + figwheel <3, pisanie kodu wspólnego dla frontu i backendu, transducery, czy makra.

Końcąc dodam, że nie miałem możliwości pracy w tym języku dla innych firm :-(, ale za to tworzę od paru miesięcy własne komercyjne produkty w clojure/clojurescript i rzeczy jakie teraz najbardziej rzutują na pisanie kodu to ultra lekkie programowanie frontu (piszę coś w rodzaju ala CRM z wieloma dynamicznymi widokami jak w jira - to jest mega proste w odróżnieniu od tego co miałem w javascript) + możliwość kodowania wszystkiego w jednym spójnym języku (front + backend, a w planach mam nawet mobilne rzeczy).

1 użytkowników online, w tym zalogowanych: 0, gości: 1