Jak stworzyć własny język programowania? Poznaj Krzysztofa Wende i jego Elchemy
Krzysztof Wende podjął się wyjątkowego wyzwania: postanowił stworzyć własny język programowania, który spełniłby wszystkie jego potrzeby. Projekt pisał po godzinach, opublikował na GitHubie, by sprawdzić, czy ktoś jeszcze zaciekawi się jego pracą. Zainteresowanie go zaskoczyło, ale nie zawsze spotykał się z przyjemnymi opiniami programistów.
Krzysztof jednak nie przejął się krytycznymi komentarzami i dalej, wraz ze znajomymi, rozwijał język Elchemy (wersję demonstracyjną znajdziecie tutaj). A wszystko po to, by ludzie zainteresowani programowaniem, używając Elchemy, zrozumieli, że „programowanie funkcyjne jest nie tylko dużo prostsze, ale także dużo przyjemniejsze i bardziej efektywne”. Nie sposób było nie zaprosić Krzysztofa do wywiadu i dowiedzieć się od niego, jak tworzenie własnego języka programowania wygląda od środka.
Spis treści
Dlaczego postanowiłeś napisać odrębny język tłumaczony na Elixir?
Wirtualna maszyna Erlanga nie ma sobie równych w tym do czego została stworzona, czyli systemach rozproszonych, co bardzo ciekawiło mnie jako niszowa specjalizacja, w której mógłbym się dalej rozwijać. Od samego początku jak zacząłem zagłębiać się w język Erlang – a potem Elixir – żałowałem, że nie ma na tę platformę języka statycznie typowanego. Do tego stopnia, że przez moment nawet postanowiłem zmienić mój główny język na Scalę, do której biblioteka Akka próbuje skopiować fenomen BEAM (Wirtualna maszyna Erlanga).
Niestety limitacje Scali i Akki postanowiły, że zdecydowałem się wrócić do Erlanga (wtedy już pod postacią języka Elixir) i ‘przeboleć’ brak typów w języku. Po pewnym czasie, gdy znałem całą platformę znacznie lepiej, postanowiłem spróbować swoich sił w rozwiązaniu problemu z językiem, o którym wiedziałem już, że będzie moim głównym kierunkiem rozwoju przez następne lata.
Dlaczego postanowiłeś stworzyć własny język, a nie napisać DSL (domain specific language)?
Próbowałem. Moim pierwszym podejściem do dodania typów było wykorzystanie funkcjonalności metaprogramowania w Elixirze. Jedną z głównych funkcji, gdzie Elixir ma znaczną przewagę nad Erlangiem są właśnie tzw. makra, czyli funkcje przetwarzające kod jako strukturę danych (AST) w czasie kompilacji, zamiast w trakcie runtime’u. Zainspirowane LISPem podejście “code as data” pozwala na dowolne rozszerzenie semantyki Elixira dopóki mieści się w ogólnie zdefiniowanej syntaktyce języka. Postanowiłem, że to idealne narzędzie do próby rozszerzenia Elixira o typy.
Czego brakowało Ci w dostępnych językach, np. w Erlangu, Elixirze?
Głównym brakiem był właśnie type-checking. Ale nie tylko. Erlang, a co za tym idzie także Elixir, mimo że popularnie nazywany jest językiem funkcyjnym, mało kto wie, jest funkcyjny bardziej z przypadku niż z zamiaru. Na początku lat 80-tych, gdy Erlang powstawał w laboratoriach firmy Ericsson, jedynym celem było zaprojektowanie języka, który najlepiej potrafiłby sobie poradzić z masową współbieżnością i niezawodnością w środowisku ekstremalnego rozproszenia. Kołem napędowym Erlanga nie było znalezienie czegoś, co poszerza dziedzinę programowania o nowoczesne i odważne rozwiązania; ich celem było wynalezienie czegoś, co zadziała i spełni wymagania branży telekomunikacyjnej.
I udało im się to w sposób lekko mówiąc nadzwyczajny. Ich testy na jednym ze switchów używających Erlanga osiągnęły wskaźnik niezawodności na poziomie słynnych “dziewięciu dziewiątek” (99,9999999% w skali roku), co oznacza, że w ciągu roku system nie był osiągalny przez nie więcej niż 31 milisekund.
I tak się stało, że ich lata badań dowiodły, że osiągnięcie tego typu wyników nie jest możliwe w systemie, w którym zmienne mogą zmieniać wartość (Variables that don’t vary / Immutable variables). A tak się akurat składa, że jest to flagowy, a właściwie nawet fundamentalny element programowania funkcyjnego.
Problemem jest jednak to, że jest to jedna z nielicznych właściwości nowoczesnych języków funkcyjnych. Ani Erlang, ani Elixir nie posiadają takich funkcjonalności jak currying, separacja kodu interakcji ze światem zewnętrznym (side-effects) czy podstawowych bloków do wyrażania stanu obliczeniowego (monady).
Wszystkie te funkcjonalności można uzyskać używając dodatkowych bibliotek, takich jak elixir_currying, Witchcraft czy Elixir Railway Oriented Programming, ale oczywiście gdy coś nie jest zintegrowaną funkcją języka, to ciężko ustalić używanie jej w wieloosobowym zespole.
Na zdjęciu: tworzenie prostych datatypów w Elchemy
Czy w Elixirze nie dało się napisać DSLa na takim poziomie?
Wszystkie wymienione wyżej funkcjonalności da się dopisać do Elixira w relatywnie prosty sposób. Wszystkie poza typami. Pierwszą próbą było upewnienie się, że zakres funkcjonalności metaprogrammingu Elixira są wystarczające do “dopisania” systemu typów. Wymagało to wielu tricków, ale w końcu udało mi się napisać dodatkowy krok kompilacji, który analizowałby kod wewnątrz modułu. Następnym krokiem było napisanie inferencji typów. Do prototypu napisałem bardzo prosty algorytm inferencji typów w Prologu. Ten język idealnie nadaje się do takich zadań, a prosta unifkacja złożonych typów nie zajęła więcej niż 60 linijek kodu. To był moment, kiedy zacząłem zdawać sobie sprawę, że odrobinę wchodzę w teren wynajdywania koła na nowo.
Do Erlanga (jak i dla Elixira) istnieje narzędzie do analizy typów, nazywa się Dialyzer (coś w stylu Flow dla JavaScripta). Problem z Dialyzerem polega na tym, że wyszukuje on tylko oczywiste błędy, a jego funkcjonalność, mimo że często przydatna, jest dosyć prymitywna w swojej naturze. Wtedy uświadomiłem sobie, że składnia Erlanga czy Elixira tak długo jak pozostaje taka sama, nigdy nie będzie mogła być w pełni bezpieczna pod kątem typowania.
Żeby mieć pewność, że kod jest w stu procentach poprawny, trzeba wyznaczyć twardą granicę, pomiędzy tym kodem, który jest bezpieczny, a tym, który nie.
Od czego zacząłeś tworzenie własnego języka?
Kiedy już zrozumiałem, że żeby wprowadzić system typów potrzebna jest odrębna składnia, zacząłem szukać wśród istniejących alternatywnych języków na BEAM, a warto wspomnieć, że jest ich kilka. Z tych najbardziej znanych można wymienić:
1. Te wzorujące się na LISPie: Joxa, LFE i Clojerl, które stosują dynamiczne typowanie.
2. Oraz te wzorujące się na ML: MLFE (Alpaca) oraz Purerl.
Jako główne wzorce postanowiłem obrać MLFE i Purerl, z oczywistych względów, że są to projekty usiłujące wdrożyć tę samą ideologię, co Elchemy, czyli statyczne typowanie na BEAMa.
Głównym błędem (moim zdaniem) jaki popełnia Alpaca, jest wynajdowanie całego języka na nowo, praktycznie bez fundamentów (poza ideologią wziętą z rodziny języków ML), co wymusza nie tylko wynajdywania na nowo istniejących rozwiązań, ale także zwiększa prawdopodobieństwo popełnienia błędów we wczesnej fazie rozwoju.
Języki jako projekty są ogromnym przedsięwzięciem i zdecydowanie bezpieczniejszym podejściem jest użycie jakiejś bazy i inkrementacyjne jej modyfikowanie pod potrzeby projektu. Podejście bardziej podobne do ideologii Agile, niż tworzenie całego projektu od zera.
Za podstawę postanowiłem użyć Elma, co pozwoliło mieć nie tylko bazowe biblioteki do formułowania kodu, ale także dostęp do narzędzi formatujących czy pluginów do edytorów. Za język docelowy za to Elixira, który wbudowane ma wsparcie pisania dodatkowych kompilatorów oraz masę świetnie napisanych narzędzi.
Na zdjęciu: tworzenie prostych datatypów w Elchemy
Jakie miałeś założenia? To był projekt czysto hobbystyczny czy założeniem było to, że udostępnisz go innym, by mogli w nim coś napisać?
Na początku był to projekt wyłącznie dla mnie, żeby odkryć czy jest wykonalny, nauczyć się jak tworzy się kompilatory. Bardzo szybko wrzuciłem go na GitHuba, żeby mieć narzędzie do planowania rozwoju projektu. Zanim zdążyłem się zdecydować, że to coś na co faktycznie spędzę więcej czasu, znalazło się wiele osób, które bardzo zainteresowały się takim rozwiązaniem i uświadomiły mi, że jest to faktycznie coś, co może przydać się nie tylko mnie. Zanim się zorientowałem pojawiło się kilka postów na różnych forach, Slacku i Redditcie odnośnie projektu, gdzie w komentarzach były prowadzone bardzo zażyłe dyskusje odnośnie tego czy taki projekt może faktycznie działać i mieć sens.
Wtedy zrozumiałem, że nie tylko jest to ciekawe rozwiązanie, ale również coś o czym ludzie myśleli i było parę nieudanych prób wcielenia tego pomysłu w życie, w tym nawet taka w wykonaniu jednego z głównych twórców języka Elm.
Czy już podczas opracowywania planu stworzenia języka chciałeś przenieść najciekawsze rzeczy z innych języków? Jakie były to elementy?
Zdecydowanie miałem parę funkcjonalności, które wiedziałem, że chcę żeby znajdowały się w języku. Byłem pewien, że chcę zostawić pattern matching, ponieważ jest to jedna z najlepiej rozwiniętych funkcjonalności Erlanga i Elixira, a ludzie przychodzący z jednego z nich (którzy są głównym planowym odbiorcą projektu) na pewno odczuwali by jego brak.
Z funkcjonalności Elma, na których bardzo mi zależało to przede wszystkim Union Type’y, czyli możliwość definiowania własnych typów jako “Discriminated Union”. Bardzo ważny był też dla mnie currying, czyli możliwość częściowej aplikacji funkcji. Pozwala to na reprezentowanie kodu w dużo bardziej ekspresywny i zwięzły sposób.
Jedną z głównych cech Elma, którą chciałem zachować jest separacja side-effectów od czystego kodu. Erlang ani Elixir nie kładą na to nacisku a z mojego doświadczenia taka praktyka znacznie ułatwia zapobieganie oraz eliminowanie błędów w działającym systemie.
Na jakie problemy napotkałeś na początku?
Pierwszym głównym problemem było pozwolenie na wyrażenie wszystkich konstrukcji Elixira z użyciem Elchemy, jak i w odwrotną stronę, sposób przedstawienia w czytelny sposób konstrukcji Elchemy za pomocą kodu Elixira.
Wraz z rozwojem projektu bardzo szybko wypracował się mały wewnętrzny DSL w Elixirze, który wyraża konstrukcje Elchemy bez przytłaczającej ilości kodu, która inaczej byłaby niezrozumiała dla czytającego kod.
Kolejną największą przeszkodą jest napisanie systemu efektów, który byłby w pełni statycznie typowany a jednocześnie dobrze współgrał z architekturą OTP (Open Telecom Platform), która jest nieodłączną częścią programów pisanych pod Erlang VM.
Na jakim etapie dzisiaj jest język Elchemy? Co udało się zaprogramować z jego wykorzystaniem?
Dzisiaj, czyli właściwie dokładnie rok od pierwszej linijki projektu, Elchemy jest w wersji 0.6.4, 153 stabilną wersją z kolei. Kompilator napisany jest sam w sobie (Elchemy napisany jest w Elchemy), potrafi skompilować samego siebie i poprawnie zinterpretować cały kod, co uważam za wystarczający dowód, że projekt wyszedł już z etapu bycia eksperymentalnym “Proof of concept”.
Od wersji 0.6 wspierana również jest kompilacja inkrementalna, co oznacza, że kompilator rozpoznaje zmienione pliki i przetwarza tylko te, które zostały modyfikowane od ostatniej kompilacji. Znacznie zmniejszyło to czas “feedback loopa”. Dla przykładu kompilując Elchemy samym sobą (około 7000 linijek kodu), zamiast czekać 17 sekund na przetestowanie nowych zmian, wystarczy 0.1 sekundy.
Poza samym sobą, nie było jeszcze żadnych większych projektów napisanych z użyciem Elchemy, wyłącznie parę projektów sprawdzających możliwości języka, głównie spod mojej ręki. Był również jeden projekt symulatora pokerowego pisany przez studenta Lomonosov Moscow State University, który miał zaprezentować potencjał języka. Wynik gotowości do użycia w produkcji został ogłoszony jako negatywny, z powodu braku OTP w Elchemy, ale raport przedstawia język jako pełen potencjału i podsumowuje go zdaniem: “So, we can say that Elchemy is good to write safe solid pieces of pure business logic. Pure functions without side-effects […]”
Głównymi punktami rozwoju na przyszłość Elchemy jest system side-effectów, wsparcie dla Elixirowego metaprogrammingu oraz interfejs do pisania elementów OTP.
Społeczność GitHuba ciepło przyjęła Twój projekt. Czy ktoś postanowił pomóc Ci w rozwijaniu języka? W jaki sposób dołożył swoją cegiełkę do tego projektu?
Wydaje mi się, że ogromny wkład w moją motywację do rozwoju Elchemy miało to, że od samego początku miałem dwie osoby, z którymi mogłem zderzać i dyskutować pomysły na rozwiązania do Elchemy – Tomka Cichocińskiego (@Baransu) i Kubę Hajto (@hajto). Tomek dodaje różne funkcjonalności do Elchemy w wolnym czasie.
Z ludzi poznanych przez GitHuba było 7 osób które, dopisywały fragmenty kodu. Głównie dokumentacje, ale był też jeden programista z Australii, Colin Bankier, który napisał bardzo ważny element do standardowej biblioteki Elchemy.
Wydaje mi się, że najbardziej zaskakujący był odzew w zakresie dyskusji na temat rozwiązań architekturalnych dla Elchemy. Jest grupa ludzi z całego świata, którzy gdy tylko pojawi się nowy temat w Issues, poświęcają masę czasu na przedstawienie rozwiązań z innych dziedzin wraz z ich limitacjami czy zaletami. Pozwala to na omówienie wszystkich rozwiązań zanim nastąpi ich implementacja, co znacznie ułatwia rozwój projektu.
Jakie są zalety stworzenia własnego języka programowania?
Z zalet zdecydowanie mogę powiedzieć, że znacznie poprawiło to moje rozumienie Elixira jako języka oraz rozwiązań, które postanowił wykorzystać. Znacznie też wpłynęły ostatnie miesiące na moją wiedzę w zakresie pisania parserów i generalnych technik wykorzystywanych w pisaniu kompilatora.
Ale oczywiście chyba największą i najbardziej nieocenioną zaletą jest możliwość oglądania, jak Twój pomysł jest doceniany przez ludzi z całego świata i ile serca wkładają w rozwój czegoś, co próbuje zrealizować Twoją ideę. Tego nie na pewno nie da się niczym zastąpić.
Na zdjęciu: system typów w czytelny sposób przedstawia błędy w kodzie
A jakie są wady?
Jedną główną wadą jest samo rozwijanie dowolnego oprogramowania open source. Niezależnie od tego czy ludzie coś dostają za darmo, czy za to płacą i tak będą dużo wymagać. Często zdarza się, że otrzymuję wiadomości od ludzi, którzy albo mają jakiś konkretny problem do rozwiązania, albo jakiś w ich opinii pomysł wart wdrożenia. Moim obowiązkiem jako głównego “patrona” projektu jest rozwiązać ich problem, lub chociaż odpowiedzieć dlaczego teraz (lub wcale) nie da lub nie powinno się tego robić.
Drugim równie frustrującym problemem, jest fala hejterów w sieci, którzy w niekonstruktywny sposób krytykują przydatność projektu. Zaskakujące jest to w jak nieelegancki sposób potrafią się zachowywać znani i szanowani ludzie ze świata programowania, tylko dlatego, że chcą wyrazić swoją prywatną opinię. Jednym z najbardziej dotkliwych była opinia twórcy języka Elm, który mimo że do samego projektu nie miał problemu, to wyraził się dosyć kolorystycznie na temat Elchemy (wtedy jeszcze Elmchemy, nazwa zmieniona z dokładnie z tego powodu). Nazwa jego zdaniem żerowała na popularności języka Elm.
Wydawało mi się to o tyle absurdalne, że specjalnie wymieniałem nazwę języka Elm, żeby oddać swojego rodzaju honor językowi, na którym głównie się wzorowałem i nie zawłaszczać samemu sobie pomysłów na masę rozwiązań, które zastosowałem w Elchemy. W zamian otrzymałem za to oskarżenie o używanie znaku handlowego Elm w celach rozgłoszenia projektu. I tak, nie ukrywam, że chciałem zaciekawić projektem również programistów języka Elm, ale nie spodziewałem się, że może to zostać przez kogoś odebrane w negatywny sposób. Prywatna rozmowa z twórcą Elma zadecydowała o zmianie nazwy projektu z Elmchemy na Elchemy.
Ale podsumowując pozytywne i negatywne opinie ludzi z sieci muszę powiedzieć, że negatywy kompletnie blakną w świetle zatrważającej ilości ludzi zainteresowanych i “zajawionych” na pomysł Elchemy. Mimo że czasami trzeba sobie poradzić z negatywną opinią, to jest w to w największym stopniu jeden procent dyskusji, które udało mi się przez ostatni rok poprowadzić z ludźmi; a nie ma lepszego bodźca napędowego niż absorbująca dyskusja z inteligentnymi ludźmi!
Jakie masz plany rozwoju języka Elchemy?
Już od ponad sześciu miesięcy prowadzimy dyskusję na temat najlepszego sposobu implementacji odpowiedniego systemu side-effectów, ale aktualnie chcę się skupić na tym, żeby Elchemy było bardzo dobre w tym co już potrafi, zanim zaczniemy rozszerzać ilość funkcjonalności.
Warto wspomnieć, że sam rozwój języka jako projektu, to tylko mała część całego przedsięwzięcia. Równie – jak nie bardziej – ważne jest pisanie dokumentacji, wprowadzeń dla początkujących, poradników czy prowadzenie dyskusja na temat decyzji projektowych, które będą miały wpływ na przyszły rozwój języka.
Na przykład o tyle, o ile od początku tego roku sam język nie rozwinął się dosyć poważnie, tak powstała jego strona internetowa. Powstała również strona, na której można przetestować język wraz z kompilacją i uruchamianiem prostych programów z poziomu przeglądarki, co pozwala zademonstrować jego własności bez tracenia czasu na pobieranie i instalowanie go na własnym komputerze. Tak samo jak powstała obszerna dokumentacja opisująca i tłumacząca każdą podjętą decyzję jak i plany na przyszłość rozwoju języka.
Aktualnie jednak dla języka jako samego projektu wydaje mi się, że najbliższe trzy główne release’y będą zawierać w tej kolejności:
1. Wsparcie dla metaprogrammingu, pozwoli to na bezpośrednie używanie wielu ważnych bibliotek do Elixira jak ExUnit do testów jednostkowych, Ecto do modelowania interfejsu bazy danych czy Plug do tworzenia middleware’ów serwerów HTTP.
2. System efektów – Do interakcji z systemem operacyjnym, bazami danych czy systemem plików.
3. Wsparcie dla OTP – co nie jest właściwie częścią języka, a tylko biblioteką do niego, ale jest to nie mniej ważny element całego ekosystemu.
Jak chciałbyś, aby był postrzegany przez innych programistów?
Bardzo zależy mi na tym, żeby Elchemy był postrzegany jako język przyjazny dla początkujących, ale jednocześnie ekspresyjny i nie ograniczający ludzi chcących pisać bardziej zaawansowane systemy. Bardzo ważnym elementem jest czytelność i prostota w użyciu. Tak samo jak w Elmie, kiedykolwiek popełniamy jakiś błąd, kompilator zawsze stara się najlepiej jak potrafi z dostępnych mu informacji domyślić się o co nam chodzi i zaproponować rozwiązanie problemu.
Uważam, że dużym problemem języków funkcyjnych jest ich wysoki próg wejścia czy przyswajalność. Dobrym przykładem jest na przykład Haskell, który jest wręcz legendarnie uważany za najtrudniejszy z języków programowania.
Chciałbym, żeby z użyciem Elchemy ludzie zainteresowani programowaniem zrozumieli, że programowanie funkcyjne jest nie tylko dużo prostsze, ale także dużo przyjemniejsze i bardziej efektywne. Przestrzeganie zasad programowania funkcyjnego owocuje w dużo krótszy i czytelniejszy kod, a programy w nim napisane są mniej podatne na błędy i dużo łatwiejsze do debugowania. Chciałbym, żeby pisanie w Elchemy zamiast wymuszać te zasady na użytkowniku, samo w naturalny sposób prowokowało pisanie używając najlepszych praktyk poprzez bardzo minimalną składnię.
Za to dla programistów, którzy są bardziej doświadczeni, chciałbym żeby Elchemy było kolejnym narzędziem, które poprawia komfort pracy. Zwiększa bezpieczeństwo w krytycznych częściach systemu, oferuje system typów o najwyższym poziomie inteligencji, a jednocześnie nie jest ograniczeniem dla piszącego, jak w wielu innych językach, gdzie system typów czy side-effectów próbuje wejść Ci w drogę i wymusza poprawianie kodu, tylko po to by je zadowolić.
Krzysztof Wende. Startupowiec, pasjonat praktycznych zastosowań programowania.
W wieku 18 lat założył ze wspólnikiem z Nowego Jorku startup – aplikację konkurującą z Uberem, by rok później sprzedać go korporacji zatrudniającej ponad 700 pracowników na całym świecie. Wrócił ze Stanów do Polski, żeby założyć własny software house i pomagać innym startupom w implementacji eksperckich systemów rozproszonych. Dzisiaj tworzy język Elchemy i pracuje zdalnie do firmy w Wielkiej Brytanii.