poniedziałek, 4 kwietnia 2016

Trochę o programowaniu interfejsów użytkownika

Dawno, dawno temu było sobie JQuery. Co było wcześniej już chyba nikt nie pamięta. Ci, którym tak jak mnie coś tam świta, pamiętają jedynie, że JQuery przyniosło wybawienie. Skończyły się czasy żmudnego odwoływania się do elementów po konkretnej ścieżce (lub nadawania wszędzie id), a co więcej skończyły się różnice w pisaniu kodu pomiędzy przeglądarkami. To drugie na początku nie miało żadnego znaczenia. Wszyscy używali IE więc w sumie to była tylko jedna przeglądarka. Ale nie o JQuery będzie dzisiejszy post lecz o podejściu do tworzenia interfejsu użytkownika w Javascriptcie.

Zatem było sobie JQuery. Później pojawiło się JQuery UI. Rozszerzało ono, jeśli się nie mylę, prototyp JQuery o funkcję widget. Jak sama nazwa wskazuje pojawiły się więc widgety. Nagle na formularzu mogliśmy mieć klikalny kalendarz, wyskakujące okienko zamiast brzydkiego alertu, jakieś zakładki, przyciski i pewnie jeszcze parę innych elementów. Niektóre z tych widgetów potrzebowały stanu (np. po wyborze daty z kalendarza, gdzieś trzeba tę datę zapisać). Gdzie go przechowywano? Bezpośrednio w elementach. Czy to w jakimś ukrytym input'cie (diplay: none lub po prostu <hidden>), czy za pomocą magicznego wywołania $.data na elemencie. I na tym JQuery powinno się zakończyć. Niestety wyobraźnia (czy też jej brak) programistów i zapewne ich aktualne doświadczenie powodowały, że szli oni o krok za daleko. Zaczęły powstawać różnorakie dynamiczne widoki. Nie lada sztuką było odnalezienie się w powiązaniach między poszczególnymi elementami. Problemy zaczynały się kiedy to kliknięcie na przycisk ukrywało element formularza, a jego walidacja decydowała czy walidować element na podstawie jego widoczności. Wystarczyło najpierw wypełnić niepoprawnie pole i później je ukryć by w ten podstępny sposób błędne dane przedostały się do serwera.

Każdy większy widok kończył się niekończącym śledzeniem podpiętych zdarzeń. Na szczęście zarówno po stronie serwera jak i klienta zaczął pojawiać się wzorzec MVC. Oddzielał on widok od danych, które w przypadku programowania w JQuery oznaczały dokładnie to samo (przechowywanie stanu w DOMie oraz logika zależna od DOMu). Wzorzec ten po stronie Javascriptu mocno upowszechnił Backbone.

Backbone

Sama biblioteka powstała mniej więcej w tym samym czasie co Angular. Jednak na początku to Backbone cieszył się sporą popularnością. W bardzo prosty sposób definiowało się model danych jako po prostu osobny obiekt, który posiadał różne ciekawe metody. Jedne z nich umożliwiały synchronizację modelu z serwerem, inne dawały możliwość łatwego walidowania danych czy reagowania na zmiany. W widokach nadal używało się JQuery choć Backbone za pomocą biblioteki Underscore wprowadził obsługę szablonów. Walidacja nigdy więcej nie zależała już od widoku. Gdy pojawiał się błąd wiadomo było gdzie szukać.

Jednak pojawił się inny problem. Widoki były świetne ale tylko dopóki się nie rozrosły. Na początku każdy zaczynając projekt odświeżał sobie widok na każdej zmianie modelu. Widok nie analizował niczego tylko usuwał co było wyświetlone i dodawał od nowa. Kod był banalnie prosty i łatwo było go testować. Niestety bardzo szybko to podejście skutkowało utratą wydajności. Ponadto tworzenie większości widoków i modeli stało się żmudnym zadaniem typu kopiuj-wklej. Powstało więc dużo frameworków i pluginów typu Marionette czy Thorax, które tę sprawę ułatwiały. Angular z kolei skupiał się na czym innym.

Angular

Idea Angulara polegała na minimalizowaniu pisania kodu w Javascript'cie. Kod miał być bardziej deklaratywny jak HTML. Angular w swoich szablonach wykorzystywał więc wiele dodatkowych atrybutów zaczynających się od "ng". Pisanie w Javascript'cie ograniczało się do pisania kontrolerów, gdzie umieszczaliśmy nasze funkcje i dane, które wykorzystywał widok. Dostarczał on też mnóstwo rozwiązań na niemal każdy problem (np. logowanie). Pozwalało to bardzo szybko wystartować z projektem. Jednak jak to zwykle bywa im dalej w las tym więcej drzew. Prędzej czy później do Angulara trzeba było coś dopisać. A to nie było już takie banalne jak w przypadku Backbone'a. A szybkość działania? W Angularze nie mieliśmy specjalnie kontroli nad tym co kiedy się odrysowuje. Najczęściej następowało to po zmianie modelu. W większych aplikacjach stanowiło to spory problem.

React

Facebook doskonale ten problem rozumiał. Wszyscy chcieli mieć prosty widok, bez zbędnych ifów i jeszcze musiał być szybki. Jak tę szybkość można było uzyskać? No jedynie analizując co jest narysowane oraz co się zmieniło by nie odrysowywać za dużo. Samo odrysowywanie to oczywiście uogólnienie. Podpinanie zdarzeń i usuwanie elementów też zabiera istotny czas. A co jak byśmy sobie narysowali widok na boku i porównali z tym co mamy aktualnie w DOMie? Tak narodziła się idea VirtualDOM. Niczego od razu nie odrysowujemy, najpierw robimy to w pamięci co jest bardzo szybkie, a później porównujemy i nanosimy odpowiednie, najczęściej mikroskopijne, zmiany. Widok jest prosty, model jest prosty i kontroler pozostaje prosty. Nie musimy też, tak samo jak w Angularze, ręcznie decydować co kiedy odrysować. React pilnuje, który model się zmienił, i który widok należy odświeżyć.

Co dalej?

Tutaj tak naprawdę zaczyna się sedno tego postu. Pewnie każdy wie, że Angular 2 pisany jest w TypeScript'cie. React z kolei wykorzystuje Javascript w o wiele bardziej rozbudowanej wersji niż tam wspierana przez przeglądarki. Poza tym pojawiło się wiele kompilatorów innych języków do Javascriptu. Na dodatek cały czas toczy się projekt WebAssembly, który jak nazwa wskazuje, ma wprowadzić do przeglądarek obsługę bytecode'u by móc pisać cięższe aplikacje (jak np. gry). Ale po co to komu?

Javascript po prostu muli. Da się w nim pisać, tak aby nie mulił ale taki kod nie przypomina w niczym kodu czytelnego. Weźmy taki przykład:

var x;
var result = 1;
for (x = 0; x < 10; x++) {
    result += x*x*result - 1;
}

Kod oczywiście nic sensownego nie robi ale załóżmy, że tak jest. W pewnym momencie dochodzimy do sytuacji, że wzór obliczeń wykorzystywany w pętli jest potrzebny w kilku miejscach. Najprościej jest więc go skopiować. Ale z drugiej strony jak utworzymy sobie funkcję to będzie łatwiej nim zarządzać. Niestety wywołanie każdej funkcji kosztuje, a już najwięcej w Javascript'cie kosztuje wywołanie funkcji rekurencyjnych. Optymalny kod jest najczęściej nie do strawienia.

Z drugiej strony taki kompilator to może sobie optymalizować jak chce. W naszym przykładzie mógłby wziąć ciało naszej funkcji i powrzucać je w każde miejsce jej wywołania (tzw. inlining). W tym właśnie celu (optymalizacji) powstał Google Closure Compiler. Jest to kompilator z Javascriptu do Javascriptu. Potrafi zoptymalizować i znacząco skrócić kod jeśli tylko trzymamy się jego wytycznych. Szybkość działania nie jest jednak jedynym problemem. Poprawność i łatwość utrzymania jest, jeśli nie ważniejsza to przynajmniej równie ważna. Dlatego warto się przyjrzeć dostępnym technologiom.

TypeScript

Jest to język rozszerzający Javascript o klasy, interfejsy i proste typy danych. Statyczne typowanie ułatwia tworzenie kodu bo o jego spójność dba kompilator. Jednak same typy to trochę mało. Dynamiczne języki dla odmiany działają na zasadzie, nie ważne jaki typ zostanie przekazany, dla większości powinno działać poprawnie (do pozostałej części dopisze się unit testy). I też programiści dają radę. Nie podzielam więc wizji Angulara. Jak już wybierać to coś co daje nam konkretne zalety.

ClojureScript

ClojureScript to oczywiście Clojure czyli dynamiczny język funkcyjny. Zaczynając pisać w czymś takim mamy tę zaletę (poza tym, że jest funkcyjny), że wiele bibliotek do Clojure już powstało a teraz możemy je wykorzystać (o ile nie korzystają z Javy) do części webowej. Dostępna jest w tym języku bardzo dobra biblioteka o nazwie Om, która wykorzystuje Reacta. Wykorzystuje i właściwie rozszerza. Sprawia, że widoki odrysowują się rzadziej i programista ma nad tym większą kontrolę. Co tu dużo mówić, fajna sprawa.

PureScript i Haskell

W Haskellu mamy dostępnych kilka kompilatorów do Javascriptu. Jest GHCJS, które to potrafi skompilować większość kodu Haskellowego ale kod wynikowy jest dość duży. Jest też haste, które nie potrafi kompilować kodu wykorzystującego TemplateHaskell ale za to wynikowy Javascript jest o wiele lżejszy i szybszy. Do tego jeszcze mamy całkiem osobny projekt o nazwie PureScript, który jest osobnym językiem naśladującym Haskella. W tych technologiach (jak i w powyższych) musimy wykorzystać, jakąś bibliotekę wykorzystującą np. Reacta lub VirtualDOM by nie powielać podejścia Backbone'a czy Angulara. Ale za to mamy naprawdę porządne narzędzie do pisania aplikacji.

Elm

Ten język zostawiłem na koniec, ponieważ według mnie jest on najbardziej dopracowany pod kątem tworzenia interfejsów. Daje on nam nie tylko bezpośredni dostęp do VirtualDOMa ale też wprowadza programowanie w stylu FRP (Functional Reactive Programming). W tym stylu w uproszczeniu za pomocą sygnałów łączymy aplikację w całość. Sygnały mogą pochodzić z widoku (np. po kliknięciu) albo z zewnątrz (np. wywołanie ajaxowe). Mogą one być stałe (np. co 60 sek.) albo na żądanie. Elm jest podobny do Haskella ale o wiele prostszy. Jednak mimo to mamy całkowitą kontrolę nad efektami, mamy język funkcyjny oraz mamy FRP. A do tego jeszcze jest bardzo szybki. Naprawdę warto dać mu szansę.

piątek, 1 kwietnia 2016

Style programowania

Ostatnimi czasy języki funkcyjne zyskały bardzo na popularności. Widać także jak "dojrzałe" imperatywne języki programowania przybierają coraz to nowe formy dodając elementy funkcyjne (myślę o c++, php, javie). O tych drugich właśnie będzie traktował ten post. I nie mam zamiaru pisać dlaczego akurat tak się dzieje lecz jak to wykorzystać.

To co chcę napisać najłatwiej będzie przyswoić patrząc na przykłady. Zacznijmy więc od takiego problemu:

Mamy tablicę z trzema elementami. Niech będą to 1, 2, 3. W PHP wyglądałoby to tak:

$tablica = [1,2,3];

Chcemy każdy z tych elementów zwiększyć o jeden. Sprawa jest banalna (celowo nie użyłem żadnej pętli):

$tablica[0]++;
$tablica[1]++;
$tablica[2]++;

Widzimy gołym okiem, że jest to styl imperatywny. Kod czytamy z góry na dół, każda kolejna linia to kolejna instrukcja, która zostanie wykonana. Teraz spójrzmy jak możemy to napisać w jakimś języku funkcyjnym, np. w Haskellu:

arrayIncrement :: [Int] -> IO [Int]
arrayIncrement a = do
  a1 <- return $ a!!0 + 1
  a2 <- return $ a!!1 + 1
  a3 <- return $ a!!2 + 1
  return [a1, a2, a3]

Oczywiście jest to bardzo dziwny Haskell. No właśnie tylko dziwny jak co? Ten kod naśladuje to co napisałem wyżej w PHP. Pewnie, że da się napisać bardziej zbliżony kod ale to co chciałem pokazać to to, że pisanie w języku funkcyjnym nie oznacza wcale, że piszemy funkcyjnie. Jasne, że to wszystko są funkcje ale kod czyta się dokładnie tak samo jak kod imperatywny. Każda linia jest właściwie osobną instrukcją wykonywaną w zadanej kolejności. Pytanie tylko czy to coś złego (w końcu kod działa poprawnie). Nie ma się co oszukiwać, nawet w Haskellu operacje IO pomimo bycia monadą są pisane imperatywnie bo po prostu inaczej się nie da.

Wszyscy piszemy imperatywnie

Każdy program zawiera jakąś część imperatywną. Wszędzie tam gdzie ważna jest kolejność wykonywania akcji (czyli funkcji, które mają jakiś efekt, np. wypisanie czegoś na ekran) kod pisany jest w sposób imperatywny. Np. łącząc się do Postgresa zazwyczaj chcemy najpierw ustawić parametr search_path, a dopiero później wykonać zapytanie. Samo ustawienie nie zwraca nic, co byłoby potrzebne przy wykonywaniu zapytania. Nie można więc wydedukować co kiedy należy wykonać inaczej niż z kolejności określonej przez programistę.

Niemal każdy program zawiera także część funkcyjną i logiczną. Są to dwa główne paradygmaty programowania poza imperatywnym. Szkopuł w tym, że te części mogą być zapisane imperatywnie, a jak pokazałem wyżej, nie da się każdej imperatywnej części zapisać w pozostałych dwóch stylach. Większość programów z jakimi miałem do czynienia była pisana od początku do końca imperatywnie, mimo iż wiele by zyskały gdyby odpowiednie ich części zapisać w odpowiednich stylach.

Część funkcyjna

Czym jest ta część najłatwiej będzie wydedukować na podstawie definicji czystej funkcji (ang. pure function). Jest to funkcja pozbawiona efektów, której wynik zależy tylko od wartości przekazanych parametrów. Nie może ona, np. łączyć się z bazą danych ani wypisywać nic na ekran. Czy ona w ogóle może coś robić? Może przekształcać jedną wartość (lub wartości) w inną, A -> B.

Jest to moim zdaniem największa część większości programów. Dla przykładu, niemal wszędzie wykorzystuje się jakąś pochodną wzorca MVC, tzn. zawsze mamy jakiś model i jakiś widok, który go wyświetla. W aplikacjach webowych jest to najczęściej operacja typu: Model -> HTML, czyli dokładnie to o co nam chodzi. Przekształcenie jednego typu na drugi.

Jak najprościej zacząć pisać funkcyjnie? Miejsce, od którego łatwo zacząć już podałem. Natomiast technicznie to zależy od języka. Dla przykładu w takim PHP mamy do dyspozycji funkcje anonimowe (lambdy) oraz cały szereg funkcji zaczynających się od array_, np. array_map. Warto więc w tych miejscach zamienić kod wykorzystujący pętle for, while, foreach na array_map lub/i array_reduce. Taki kod zrobi się bardziej czytelny i łatwiej będzie go testować. Czytelny robi się dlatego, że nazwa funkcji array_map mówi już co będziemy tutaj robić, a mianowicie mapować każdy element jakiejś kolekcji na inny. Dla porównania sformułowanie foreach mówi nam tylko tyle, że będziemy robić coś z każdym elementem kolekcji. Właściwie to pomimo nazwy nawet nie wiadomo czy z każdym (jest wiele sposobów na wyjście z pętli) ale najgorsze jest to, że nie wiemy czy na każdy element chcemy wysłać głowicę jądrową czy tylko wyświetlić jego nazwę na ekranie.

Prostota testowania z kolei wynika z cech czystych funkcji. Jeśli funkcja zależy tylko od swoich argumentów to aby ją przetestować należy je dobrać i porównać otrzymany wynik z tym oczekiwanym. Nie potrzeba pisać żadnych mocków czy stubów.

Ważna jest też przewidywalność. Z array_map nie da się wyjść wcześniej (no chyba, że rzucimy wyjątek) co powoduje, że szukanie błędów jest o wiele prostsze. No bo jak inaczej nazwać badanie ciała pętli, w której występują instrukcje typu break lub continue jak nie drogą przez mękę?

Część logiczna

Ciekaw jestem ile osób jest w stanie wskazać część logiczną w swojej aplikacji. A gwarantuję, że występuje ona niemal we wszystkich. Zacznijmy ponownie od przykładowego problemu do rozwiązania. Tym razem weźmy tablicę, z której chcemy wyciągnąć co drugi element i to zwiększony o 1.

Imperatywnie:

$tablica = [1,2,3,4,5,6];
$nowa_tablica = [];
foreach ($tablica as $k => $e) {
  if ($k % 2 == 0) {
    $nowa_tablica[] = $e + 1;
  }
}
Funkcyjnie
array_map(
    function ($e) { return $e + 1; }, 
    array_filter(
        [1,2,3,4,5,6], 
        function ($k) { return $k % 2 == 0; }, 
        ARRAY_FILTER_USE_KEY
   )
);
Logicznie (pseudo kod):
[ x | y ∈ [1,2,3,4,5,6]
    , idx(y) mod 2 = 0
    , x = y + 1 ] 

Jeśli masz kłopot ze zrozumieniem ostatniego kodu to można go przeczytać jako: znajdź mi każde x, które jest większe o 1 od y, które to należy do ciągu [1,2,3,4,5,6] i jest jego parzystym elementem. Ciekaw jestem czy już widzisz, gdzie w swoim kodzie wykorzystujesz analogiczny zapis. Jeśli nadal tego nie czujesz to co powiesz jak zapiszę to nieco inaczej:

select y.v + 1 as x
from y -- zakładam, że y to tabela z kolumną v z wartościami 1,2,3,4,5,6
where y.id modulo 2 = 0

OK, teraz chyba już każdy załapał. No to na czym polega ten styl? Zamiast pisać co chcemy po kolei zrobić albo co chcemy zamienić na co, podajemy po prostu wszystko co wiemy o interesującej nas wartości, a jak to znaleźć niech martwi się implementacja. Wynika z tego, że ten styl jest najlepszy właśnie do wyszukiwania. Deklarujemy z góry wszystkie relacje interesującej nas wartości z innymi, które znamy (wypisujemy fakty). Ale czy sprowadza się to tylko do używania silnika bazy danych? Oczywiście, że nie. W takim Clojure mamy moduł core.logic, w Haskellu mamy biblioteki z implementacją miniKanrena lub chociaż Monad.Logic, a w PHP? W PHP nie ma nic bezpośrednio ale np. w poprzednim przykładzie zamiast pisać połączenie array_map i array_filter mógłbym stworzyć sobie jakąś funkcję array_find:

function array_find(array fakty) {
...
}

array_find(["member-of" => [1,2,3,4,5,6],
            "index" => function ($i) { return $i % 2 == 0; },
            "equals-to" => function ($y) { return $y + 1; }
           ]);

Nie chodzi tu o definicję tego jak array_find działa tylko w jaki sposób zapisałem to czego oczekuję. Mógłbym to zapisać bardziej obiektowo:

ArrayFinder::memberOf([1,2,3,4,5,6])
  ->index(function ($i) { return $i % 2 == 0; })
  ->equalsTo(function ($y) { return $y + 1; })
  ->find();

Plusy są dość oczywiste. Kod jest zapisany niemal pełnymi zdaniami jakbyśmy opisywali koledze o co nam chodzi. Testowanie jest tak samo banalne jak w części funkcyjnej i to właściwie z tych samych powodów. To co nie jest banalne to implementacja. Wygląda to fajnie i właściwie dlaczego by tych miejsc funkcyjnych nie zastąpić tym stylem? No to weźmy poprzedni przykład zamieniania modelu na HTMLa. Jakbyśmy opisali koledze to, że chcemy z imienia i nazwiska zbudować sobie diva, który będzie miał określone animacje, długość, będzie się pojawiał i znikał a do tego jeszcze imię i nazwisko będzie osadzone w osobnych divach to położyłby się i zasnął. Natomiast funkcyjnie zamiast słów będzie widać jakie obiekty budujemy i z jakich elementów. Co nie jest prawdą pisząc w stylu imperatywnym, gdzie widzielibyśmy bardziej jakie kroki po kolei podejmujemy by najczęściej dopiero na końcu zrozumieć o co właściwie nam chodzi.

To tyle na dzisiaj, mam nadzieję, że mój słowotok nikogo nie uśpił.