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ł.

sobota, 31 października 2015

Jak żyć?

Wybory się odbyły. Wszyscy są na świeżo z polityką więc nadarza się fajna okazja by przedstawić moje poglądy w tym temacie. Dlaczego po, a nie przed wyborami? Dlatego, że zamierzam przedstawić moje poglądy, a nie nawoływać byś zmienił swoje.

Jestem chrześcijaninem i libertarianinem. Niektórzy mówią, że to się ze sobą nie łączy. Nie do końca rozumiem ten tok rozumowania. Przecież Bóg stworzył człowieka by ten miał wolną wolę. Oczywiście zły wybór ma swoje konsekwencje ale niewątpliwie wolność jest naszą naturą. A libertarianizm traktuje właśnie o wolności. Nie mówi on o tym co jest dobre, a co złe. Mówi tylko o społeczeństwie, "w którym żadna ludzka własność - czy to własność osoby czy rzeczy realnych - nie podlega agresji ze strony innych, nie jest przez innych naruszana czy poddawana jakiejś obcej ingerencji" - Etyka wolności, Murray N. Rothbard, s. 124.

I zgadzam się, że czasami przekroczenie wolności może być moralne. Kiedy? Na przykład widząc umierające z głodu dziecko na ulicy i nie mając pieniędzy kradzież w celu ratowania życia byłaby uzasadniona moralnie. Lecz i wtedy godziłbym się ponieść konsekwencje tego czynu. W końcu dokonałbym kradzieży. Jak pisze Rothbard:

"Nie chodzi nam o to, czy bycie kłamcą, bycie dobrym człowiekiem, rozwijanie własnych zdolności lub bycie miłym lub podłym dla sąsiadów jest moralne bądź nie. Interesują nas jedynie takie kwestie "etyki politycznej", jak właściwa rola przemocy, sfera praw albo definicje przestępczości i agresji" (Etyka wolności, Murray N. Rothbard, s. 259).

Definicja wolności

Pisząc o wolności najpierw postaram się ją zdefiniować. Będę się dalej do niej wielokrotnie odnosił więc jest ważne byśmy ustalili najpierw pojęcia.

Moją wolność rozumiem jako brak agresji wobec mojej osoby i mojej sprawiedliwie zdobytej własności. I znów jak pisze Rothbard: "... definicja wolności lub swobody, aby została rozumiana jako "brak naruszenia ludzkiej sprawiedliwej własności", musi zostać rozjaśniona tak, by sprawiedliwość wskazywała na tytuł własności do własnej osoby, do przekształconej (niczyjej) własności oraz do owoców dobrowolnych wymian na nich zbudowanych." (Etyka wolności, Murray N. Rothbard, s. 125)

Wszyscy wiemy jak w kontekście tej definicji plasuje się system polityczny, w którym żyjemy zwany demokracją. Każdy wyznający tę samą definicję wolności co ja zdaje sobie zapewne sprawę, że demokracja to tak naprawdę agresja większości. A dotykając tego tematu nie sposób od razu nie wspomnieć o wyborach.

Demokracja?

Skoro demokracja to agresja większości to wybory są narzędziem do jej wywoływania. Również jeśli głosujesz za kimś kto chce zadać jej kres dokonujesz agresji przeciwko ludziom, którzy chcą żyć w tym systemie. I to pomimo tego, że w libertariańskim społeczeństwie mogliby sobie żyć jak chcą. Nie czynisz niczego złego, gdy ktoś tobie narzuca sposób życia. Natomiast czynisz źle, gdy narzucasz go innym.

Popularne stwierdzenie - mam nadzieję, że nie u libertarian - kto nie głosuje ten nie ma prawa krytykować jest istnym nonsensem. Wyobraźmy sobie sytuację, w której napastnik z bronią terroryzuje ofiarę lecz daje jej również możliwość poprawy swojej sytuacji poprzez agresję na innej osobie. Mógłby on wystosować takie oto słowa w kierunku ofiary:

"Nie będę Cię dłużej nękał jeśli ukradniesz pani X torebkę i przyniesiesz ją do mnie".

Mam nadzieję, że porównanie jest jasne. W tym przypadku agresorem jest rząd, który daje nam możliwość zniesienia agresji poprzez ustanowienie innego rządu w tym również dla ludzi, którzy go nie chcą. Dlaczego jeśli ofiara nie zechce skorzystać z takiej niemoralnej propozycji, miałaby nie móc dłużej krytykować oprawcy?

Jak zatem cokolwiek można zmienić? Należy pokazać i przekonać ludzi, że można żyć bez tego całego cyrku. Możemy ignorować rząd kompletnie i niezależnie od tego co stanowi prawo, postępować słusznie. Rząd bardzo chce być potrzebny bo tylko tak może zostać wybrany.

Motywacja

Zmiany jeśli mają nastąpić to muszą nastąpić oddolnie. I nie chodzi mi o to by teraz każdy poruszał się tylko po prywatnych działkach i tylko prywatnym samochodem itd. Jak pisze Jakub Wozinski, to by skutkowało narażeniem całego ruchu libertariańskiego na śmieszność. Ludzie traktowaliby libertarian jak swego rodzaju Amiszów, którzy żyją w swoim świecie. Nie znaczy to by naszą motywacją było wykazać, że rząd (policja, sądy, wojsko itp.) nie daje sobie z niczym rady. Wydaje mi się, że motywacją powinno być zbudowanie takich struktur, które lepiej będą sobie w tych zakresach radzić niż państwo.

Znacznie lepiej jest pokazać, że człowiek dobrze sobie radzi bez pomocy państwa. Jeśli prowadzisz biznes to pokaż, że możesz go prowadzić bez dotacji. Jeśli masz wybór by nie korzystać z państwowej służby zdrowia to nie korzystaj, jeśli możesz poradzić sobie bez policji i sądu to tym lepiej, jeśli musisz płacić ZUS to jeśli jesteś w stanie pokaż, że nie chcesz od niego ani złotówki itd. Rozumiem doskonale, że ludzie są w różnej sytuacji. Czasami człowiek nie widzi innego wyjścia. Potrafię sobie wyobrazić libertarianina "pracującego" w urzędzie skarbowym albo w policji. Jeśli jesteś w takiej sytuacji to przede wszystkim staraj się z niej wydostać i czyń jak najmniej szkody.

Raz jeszcze podkreślę, że nie powinno być celem doprowadzenie do bankructwa państwa, UE czy ZUSu. Celem nie jest by wykazać, że policja jest nieudolna, a sądy skorumpowane. Celem jest nauczenie ludzi, że można sobie radzić bez tego i to znacznie lepiej (albo przynajmniej z czystym sumieniem). I nie buntujmy się przeciwko płaceniu danin. Płacąc je nie czynimy niczego złego. To ludzie narzucający nam ich płacenie źle czynią. Natomiast krytykujmy ile się da. Jeśli podatki wykończą ci biznes to pokaż to. Jeśli płacenie ZUSu wpędza cię w biedę opisz to.

Strach

Strach paraliżuje zmiany. Ludzie, w mojej ocenie, boją się, że bez ZUSu ludzie wylądują na ulicy, boją się, że bez policji będzie chaos, a bez (państwowego) wojska to...

Oczywiście możemy powiedzieć, że skoro tak dużo ludzi godzi się na płacenie ZUSu by nie było innych na ulicach to też chętnie sami im pomogą bez ZUSu. Oczywiście możemy powiedzieć, że będzie mniej ludzi na ulicach niż jest dzisiaj bo ludzie biedniejsi w końcu będą mogli zaspokoić ważniejsze potrzeby niż emerytura jeśli im nikt nie będzie zabierał pieniędzy. Jeśli będą mieli co jeść i za co się leczyć zamiast odkładać na emeryturę to całkiem możliwe, że dorobią się znacznie więcej i znaczniej dłużej pożyją itd.

Możemy powiedzieć, że w najgorszym przypadku człowiek będzie pracował przez całe życie. A to myślę, że w ogóle powinien być cel każdego chrześcijanina zgodnie z tym, że "kto nie pracuje ten niechaj i nie je". Praca jest nieodłączną częścią życia człowieka, a nie tylko jego etapem ale to temat na inną okazję.

Tylko jest jedno ale. To są tylko słowa. Być może i logiczne i nie pozostawiające żadnej innej logicznej możliwości ale nadal nie jest to coś co można zobaczyć. Dlatego ważne jest by wszyscy mogli się przekonać, że można żyć z własnej woli bez ZUSu i pomagać innym, którzy sobie nie radzą. Nikt w cale nie musi liczyć na państwo.

To samo można mówić o pieniądzu. Państwo wcale nie musi go kontrolować. Możemy mówić, że pieniądz jest jak sejf bo przechowuje jakąś wartość i powinniśmy mieć możliwość wyboru najbezpieczniejszego w naszej ocenie sejfu, a nie tylko takiego, które państwo nam wskaże i do którego ono samo ma dostęp. Może nam przecież zabrać część jego zawartości poprzez inflację. Tylko, że znowu to wszystko na nic się nie zda jeśli nie pokażemy, że można żyć bez państwowego pieniądza.

Podsumowanie

Moje poglądy na rządy są jednoznaczne. Jakie by one nie były zawsze oznaczają agresję, a więc ograniczenie mojej wolności. Rozumiem też, że większość chce żyć w ten sposób ale ja nie zwracam uwagi na rząd. Ignoruję go na tyle na ile mogę sobie pozwolić. Płacę wszystko co na mnie wymusza i nie dbam o to. Mogą zabrać mi wszystko co mam. Nie żyję po to by być bogatym ani by żyć wygodnie. Nie głosuję i nie wybieram rządu. Staram się żyć w zgodzie z tym co uznaję za słuszne, a nie w zgodzie z tym co stanowi prawo. Jeśli naprawdę chcesz by ludzie zechcieli być wolni to najpierw zacznij od siebie.

poniedziałek, 6 kwietnia 2015

Tam gdzie statyczne typowanie nie wystarcza...

tam pojawia się... No właśnie, co? Zanim o tym, najpierw napiszę trochę o problemach z typowaniem. Statyczne typowanie daje nam pewność, że nigdy nie przekażemy innego typu do funkcji niż tego oczekuje. W językach dynamicznych często musimy pisać dodatkowe testy jednostkowe aby mieć jakąś dozę pewności, że dana funkcja zachowa się poprawnie dla niepoprawnego typu. Dla przykładu co ma zrobić funkcja zliczająca elementy w tablicy jeśli zamiast tablicy przekażemy jej liczbę? Mamy kilka możliwości:

  • zwracamy 0 - zazwyczaj nie wywoła to od razu większych problemów, gdyż kod wykorzystujący taką funkcję jest raczej przygotowany na obsługę pustej tablicy. Niestety w ten sposób ukrywamy błąd, którym jest oczywiście przekazanie liczby zamiast tablicy. Znalezienie go przy takim rozwiązaniu może być kłopotliwe.
  • zwracamy ujemną wartość - ujemna liczba elementów tablicy nie ma sensu więc może to sygnalizować jakiś błąd. Problem w tym, że wszędzie, gdzie używamy takiej funkcji musielibyśmy się ubezpieczyć na taką ewentualność. Tutaj znów potrzebowalibyśmy sporej liczby testów jednostkowych by jakoś to wymusić. Jeżeli tego nie zrobimy to istnieje ryzyko, że ktoś np. będzie coś przemnażał przez ujemną liczbę co znów może być trudne do wykrycia (choć łatwiejsze niż w przypadku zwracania 0).
  • rzucamy wyjątek - błąd ukaże się nam od razu więc będzie łatwiejszy w lokalizacji. Tyle, że znów musielibyśmy się przed tym zabezpieczyć w miejscach, gdzie wykorzystujemy taką funkcję. Jeżeli tego nie zrobimy to cała aplikacja się po prostu wywali. Zatem i w tym wypadku potrzebujemy sporej liczby testów.

Wszystkie te rozwiązania mają jedną zasadniczą wadę w stosunku do statycznego typowania. Musimy dopisać sporą liczbę testów by móc wykryć błędy przed uruchomieniem aplikacji. W językach ze statycznym typowaniem, błąd zostanie wychwycony już przez kompilator/interpreter więc testy dla takich problemów są zbędne. No ale czy zawsze?


Typ a wartość

Czasami mimo używania statycznych typów i tak będziemy mieli problem. Dla przykładu weźmy sobie funkcję liczącą średnią z listy liczb. Czyli przyjmujemy sobie taką listę liczb, sumujemy jej elementy i dzielimy przez ich liczbę. A co jeśli przekazana lista jest pusta? Mielibyśmy dzielenie przez 0. Statyczne typowanie nas przed tym nie ustrzeże bo pusta lista to też lista (jest tego samego typu, tak jak 0 i 1). Mamy więc dwa wyjścia. Albo korzystamy z tych samych rozwiązań, które opisałem wyżej albo tworzymy dwa typy danych: jeden to lista pusta, a drugi to lista niepusta. Problem z dwoma typami danych jest taki, że pisanie w ten sposób byłoby raczej karkołomne. W Haskellu wszędzie, gdzie zwracaliśmy np. [Int] teraz mielibyśmy coś w rodzaju Either EmptyList (NotEmptyList Int). Wychodzi więc na to, że nie pozostaje nam nic innego jak pisanie testów i modlenie się przed wrzuceniem tego na produkcję, że wszystko zadziała. Na szczęście mamy jeszcze jedno wyjście (przynajmniej w Haskellu).


LiquidHaskell

LiquidHaskell to w skrócie dodatkowy walidator kodu Haskella, który umożliwia nam pisanie w komentarzach pewnych dodatkowych logicznych warunków co do wartości danego typu. Np. w powyższym przykładzie moglibyśmy dla funkcji liczącej średnią zapisać warunek, że nie przyjmuje ona listy pustej. Warunek ten będzie sprawdzony przed uruchomieniem aplikacji, a oto właśnie nam chodzi. Tylko, że jak to działa?

Najlepiej będzie pokazać to na przykładzie. Weźmy sobie przykładową implementację gry FizzBuzz. Gra polega na wymawianiu kolejnych liczb ale jeśli są one podzielne przez 3 to mówimy "Fizz", jeśli przez 5 to "Buzz", a jeśli przez 3 i 5 to "FizzBuzz". Oto przykładowa implementacja w Haskellu:

Przykładowy wynik działania programu:

Implementacja jest bardzo prosta. Jest też bardzo podatna na błędy. Np. weźmy sobie funkcję numberType i zamieńmy Fizz, Buzz, FizzBuzz na Normal i. Program skompiluje się bez problemu, a aplikacja będzie działać błędnie.

Teraz przyjrzyjmy się implementacji z wykorzystaniem LiquidHaskell:

Różni się ona tylko komentarzami, które są interpretowane przez program o nazwie liquid, który jest dostępny po zainstalowaniu LiquidHaskell. Co możemy z nich wyczytać? Dla przykładu weźmy tę linijkę: Normal (i :: {v:Int | v mod 3 /= 0 && v mod 5 /= 0}). Mówi ona o tym, że konstruktor Normal może przyjąć liczbę i, która jest typu Int ale tylko taką, która nie jest podzielna przez 3 i przez 5. No to wypróbujmy to. Spróbujmy zrobić tak jak poprzednio czyli w funkcji numberType we wszystkich warunkach wpisać Normal i. Po tej zmianie uruchamiamy liquid fizzbuzz.hs i otrzymujemy:

liquid pokazuje nam, że w liniach 31, 32, 33 (czyli tam gdzie zmienialiśmy) nie zgadzają się typy. Dla przykładu w linii 31 oczekiwano typu Int, który nie jest podzielny przez 3 ani przez 5 ale nic na to nie wskazuje. Zauważmy, że wszystkie dane do programu są wczytywane w trakcie jego działania. Zatem liquid musi interpretować wszystkie ify w naszym kodzie by móc czegokolwiek dowodzić. Nie wymaga on więc od nas innego pisania kodu niż zwykle.


Podsumowanie

Jak widać LiquidHaskell to potężne narzędzie. Oczywiście wydłuża to sprawdzanie (i pisanie) kodu ale jak się okazuje w praktyce nie potrzeba wcale dokładać wielu dodatkowych warunków by pozbyć się sporej liczby testów jednostkowych. Taka koncepcja jest szerzej znana jako dependent types i istnieje nawet kilka języków, które takie typy posiadają. Problemem jest oczywiście czas kompilacji takich programów i jak się wydaje LiquidHaskell stara się ten problem rozwiązać. Więcej można poczytać w samouczku.

środa, 1 kwietnia 2015

BŁĄD: znak sekwencją bajtów 0xe2 0x80 0x9e kodowany w "UTF8" nie ma równoważnego w kodowaniu "LATIN2"

Jeżeli ktoś kiedyś natknął się na taki błąd to oznacza, że jego aplikacja powstawała co najmniej 20 lat temu. Komunikat oznacza mniej więcej tyle, że łączy się ona z bazą używając kodowania jednobajtowego latin-2 czy też iso-8859-2, a baza danych używa kodowania utf-8 oraz zawiera tekst, którego nie da się zakodować w latin-2 (np. tekst zawierający długi myślnik). Być może rozwiązanie, które za chwilę przedstawię nikomu się już do niczego nie przyda ale przynajmniej zostanie na pamiątkę dla przyszłych pokoleń.

Nie da się tego problemu tak po prostu obejść. Tzn. jeżeli tylko wykonywane zapytanie na bazie danych przy połączeniu w kodowaniu latin-2 zwraca dane, których nie da się w tym kodowaniu zakodować to każde jedno się po prostu wywali. Zatem jedynym rozwiązaniem tego problemu jest poprawienie tych znaków. Jednakże wyszukiwanie ich ręcznie może być bardzo czasochłonne.

Pierwsza rzecz, którą możemy zrobić to połączenie się psqlem i ustawienie odpowiedniego kodowania dla tego połączenia:

set client_encoding = latin2;

Teraz możemy próbować szukać w jakiej tabeli występuje dany znak. Wystarczy po prostu wykonać:

select * from tabela;

i jeżeli mamy szczęście to otrzymamy od razu komunikat:

BŁĄD: znak sekwencją bajtów 0xe2 0x80 0x9e kodowany w "UTF8" nie ma równoważnego w kodowaniu "LATIN2"

Pozostaje nam teraz zlokalizować wiersz i kolumnę, gdzie interesujący nas znak występuje. Mam na to dwie metody.


Pierwszy sposób

Pierwsza wykorzystuje fakt, że w komunikacie o błędzie występuje zapis szesnastkowy danego znaku. Możemy spróbować zatem go zlokalizować w taki oto sposób:

select id from tabela where encode(kolumna::bytea, 'hex') ilike '%e2809e%';

Zapytanie wykorzystuje postgresową funkcję encode, która potrafi zamienić tekst na reprezentację szesnastkową i w tej reprezentacji poszukujemy ciągu znaków zwróconego nam przez komunikat błędu. Pomijamy tylko 0x występujące w komunikacie. Metoda ta ma dwie wady:

  1. musimy znać kolumnę, w której dany znak może wystąpić (albo podać warunki na każdą kolumnę z użyciem or: ... where encode(kolumna1::bytea, 'hex') ilike '%..%' or encode(kolumna2::bytea, 'hex') ilike '%..%' or ..).
  2. komunikat o błędzie kodowania znaku zwraca nam informację tylko o pierwszym takim przypadku. Jeżeli jest ich więcej to będziemy poprawiać po jednym i czekać aż postgres zwróci nam informację o kolejnym.

Drugi sposób

Druga metoda znajduje wszystkie rekordy, które zawierają "błędne" znaki w tabeli. Niestety wykonanie go trwa znacznie dłużej.

select id
from tabela
where regexp_replace(encode(translate(kolumna, 'ęóąśłżźćńĘÓĄŚŁŻŹĆŃ', '')::bytea, 'hex'), '(.{2})', '\1 ', 'g') ~ '^[8-9a-f]| [8-9a-f]';

Jak już wspomniałem zapytanie wyłapie wszystkie rekordy w tabeli z "niepoprawnymi" znakami w podanej kolumnie. Zapytanie jest dość skomplikowane więc omówię je krok po kroku:

  1. zacznijmy od najbardziej zagnieżdżonego wywołania: translate(kolumna, 'ęóąśłżźćńĘÓĄŚŁŻŹĆŃ', '') Funkcja translate zamienia znaki występujące w jej drugim argumencie na znaki występujące w jej trzecim argumencie. Czyli w tym przypadku po prostu je usuwa. Robimy tak dlatego, że polskie znaczki są jedynymi dwubajtowymi znakami w kodowaniu utf-8, które posiadają swój odpowiednik w kodowaniu latin-2. Takie nas nie interesują. Chcemy wyłapać wszystkie wielobajtowe znaki, których nie ma w latin-2.
  2. encode(..::bytea, 'hex') - zamieniamy znaki na ich szesnastkową reprezentację. Każdy bajt jest teraz zapisany jako dwa znaki.
  3. regexp_replace(.., '.{2}', '\1 ', 'g') - zamieniamy każde dwa znaki jako x i spację. Po prostu rozdzielamy każdy bajt spacją.
  4. ~ '^[8-9a-f]| [8-9a-f]' - operator ~ sprawdza czy podany string odpowiada wyrażeniu regularnemu. Z kolei to wyrażenie sprawdza czy w ciągu znaków występuje 8, 9, a, b, c, d, e lub f, które jest albo na początku stringa albo jest poprzedzone spacją. Chodzi o to, że chcemy wyłapać czy istnieje bajt większy od 127 a taki będzie się zaczynał od tych znaków w reprezentacji szesnastkowej. Jeżeli istnieje to mamy nasz rekord.

Przy tym rozwiązaniu znów musimy testować sobie wszystkie tekstowe kolumny ale przynajmniej wyłapiemy od razu wszystkie rekordy. Możemy oczywiście napisać sobie zapytanie, które wszystkie niedozwolone znaki usunie lub zamieni na inne. Pozostawiam to już w kwestii czytelnika.

sobota, 22 marca 2014

Redystrybucja

W ostatnim czasie zebrało mi się na półce sporo nowych pozycji do poczytania. Głównie są to pozycje ekonomiczne zatem przez jakiś czas nie będę pisał o programowaniu (choć już od dawna przecież zupełnie nic nie napisałem). I tak na pierwszy ogień rzuciła się "Redystrybucja. Grabież czy ignorancja." Bertranda de Jouvenela. Gość urodził się w 1903 roku w Paryżu a zmarł w 1987. Z samej książki można wywnioskować (choć taka informacja znajduje się również w przypisach), że jest on raczej zwolennikiem państwa opiekuńczego. Jednakże, jak wskazuje sam tytuł, krytykuje on redystrybucję. Sam ten fakt sprawia, że książka jest pozycją niewątpliwie interesującą, gdyż naświetla sprawy z zupełnie innego punktu widzenia aniżeli inne tego rodzaju książki.

Książka zawiera dwa wykłady autora wygłoszone w latach 50-tych XX wieku. Pierwszy z nich rozpoczyna się właściwie wykazaniem sprzeczności socjalizmu. I już w tym miejscu autor potrafił mnie zaskoczyć myślą, która do tej pory nie pojawiła mi się w głowie. Skoro dobrą rzeczą dla ogółu jest większa ilość dóbr, dlaczego nie ma ona być dobra dla jednostki? Bogactwo w rękach jednostki jest przecież w takim ustroju godne pogardy. Odpowiedzialną sprawą za ten stan rzeczy zdają się być uczucia. Autor twierdzi, że "pragnieniu redystrybucji towarzyszy zwykle przekonanie o istnieniu skandalu". Chodzi o to, że skandaliczne wydaje się to, iż tak wiele ludzi z jednej strony żyje poniżej jakiegoś poziomu uważanego za minimalny, a z drugiej styl życia osób bogatych zdaje się być nieprzyzwoity. Problem tylko w tym, że ciężko zdefiniować ten poziom minimalny i maksymalny. Zależy on głównie od gustów części społeczeństwa i panującej mody. Której części? Ano tej najliczniejszej czyli tzw. klasy średniej. Idealnie zatem byłoby aby bogatym zabrać tyle aby ta część wyciągnęła osoby najbiedniejsze z ubóstwa. I to jeszcze w taki sposób by bogactwo krezusów przestało być nieprzyzwoite. Autor dowodzi, że takie połączenie nie jest możliwe. Na podstawie danych rządowych wylicza, że aby tego dokonać dochód maksymalny byłby o wiele niższy niż się wydaje i zahaczyłby dość mocno o dochody klasy średniej.

Czytając dalej pojawia się druga koncepcja argumentująca za redystrybucją. Sprowadza się ona do następującego zdania: "Dla ludzi bogatych utrata części dochodu znaczy dużo mniej niż odczucie korzyści osiągniętej w jej rezultacie przez człowieka biedniejszego". Czyli nie ważne czy to jest efektywne czy nie, ważne jest to, że bogaci i tak tego nie odczują. Autor stawia proste pytanie, czy takie porównanie satysfakcji może być w ogóle prawidłowo dokonane? Czy możemy z odpowiednią precyzją porównać stratę zadowolenia u jednych z jego przyrostem u drugich? Odpowiedź jest chyba jasna.

Dalej pojawia się aksjomat malejącej użyteczności. Koncepcja opiera się na tym, że kolejne jednostki jakiegoś dobra są coraz mnie warte dla właściciela. Ta zasada implikuje, że jeśli bogacz posiada 1000 banknotów o nominale 100 zł to zabranie mu jednego spowoduje niewielkie odczucie straty w porównaniu do korzyści jaką osiągnie osoba, które te 100 zł otrzyma, gdy wcześniej nie posiadała żadnego takiego banknotu. Problem jest właściwie ten sam: "satysfakcja różnych ludzi nie może być mierzona przy pomocy tej samej miary". Naprawdę wiele świetnych ripost można wyczytać z tak niewielkiej książeczki.

Na końcu pierwszego wykładu znajdziemy jeszcze informację o tym, że historycznie każdy masowy produkt był wcześniej dobrem dużo bardziej ograniczonym - dostępnym małej garstce ludzi. Zatem w takiej sytuacji pozbawienie bogatych ich bogactwa spowoduje, że nie będą pojawiać się nowe, innowacyjne produkty, które w dalszej perspektywie (jeżeli się sprawdzą) mogłyby ułatwić życie tym najliczniejszym grupom społecznym.

Drugi wykład dotyczy bardziej państwa i spojrzenia na dochód. To co mnie tutaj zaskoczyło to czym właściwie jest konsumpcja. Według de Jouvenela konsumpcja nie jest czymś ostatecznym lecz jest to środek do prawdziwego celu jakim jest życie ludzkie. Zdanie to zasługuje na chwilę uwagi. Wynika z niego, co z resztą dalej autor porusza, że dochód jest częścią życia, a zarazem środkiem do spełniania się w nim. Ten fragment jest wg. mnie właściwie najlepszą częścią tej książki. Dalej znajdziemy już tylko bardziej oczywiste fakty jak to, że państwo płaci za usługi o wiele więcej niż społeczeństwo zapłaciłoby na wolnym rynku. Czy też to, że następuje zanik czynności prowadzonych niegdyś za darmo. Np. wszelkiego rodzaju kampanie, nazwijmy to społeczne, kiedyś były prowadzone i finansowane przez ludzi, którzy np. prowadzili badania nad problemami przez nie poruszanymi. Badania również finansowali oni z własnej kieszeni. Niestety właśnie dzięki redystrybucji nie jest to dłużej praktykowane i państwo "w swej łasce" zmuszone jest subsydiować takie działania co (o zgrozo) pokazywane jest jako wzrost gospodarczy.

Książkę polecam bo mimo, że nie jest to książka (o dziwo) lekka to dużo istotnych informacji można w niej znaleźć. Czasami co prawda miałem problemy ze zrozumieniem co właściwie autor uważa na dany temat lub co próbuje przedstawić. Niemniej jednak warto przez nią przebrnąć by lepiej zrozumieć jak (podle) działają państwa i jakie są konsekwencje takich działań. Dla zainteresowanych książkę można znaleźć tutaj: http://www.fijor.com/redystrybucja-grabiez-czy-ignorancja/

środa, 14 sierpnia 2013

Funkcje na poziomie typów

Haskell posiada wiele rozszerzeń zaimplementowanych w GHC. Jednym z najbardziej znaczących okazało się Functional Dependecies[1], które odmieniło życie wielu programistom Haskella. Niektórzy jednak narzekali, że nie jest to zbyt funkcyjne podejście i w końcu powstało inne bardziej funkcyjne. Mianowicie TypeFamilies[2]. Nie chcę tu opisywać na czym polegają funkcyjne zależności i tym bardziej jak to samo wyrazić za pomocą "rodzin typów". To o czym chcę dzisiaj napisać to to co dodatkowo umożliwiają rodziny typów czyli funkcje na poziomie typów (Type Level Functions).

O co chodzi

Funkcje na poziomie typów to takie funkcje, które wykonuje kompilator, a ich treść zapisana jest w typie. Brzmi to trochę zawile więc najlepiej będzie pokazać to na przykładzie. Zaczniemy od końca. Powiedzmy, że chciałbym aby moja funkcja mogła zostać wywołana tylko na argumencie, którego największy wspólny dzielnik (NWD czy też GCD) z liczbą 4 to 4. Próba wywołania jej na innym argumencie powinna skończyć się paniką kompilatora :)

fun :: (Nat a, GCD Zero a Four ~ Four) => a -> Int

Właśnie tak będzie wyglądać typ naszej funkcji. Wyjaśnienie:

  • Nat a - a jest liczbą naturalną, treść klasy Nat pokażę niżej,
  • GCD Zero a Four - typ GCD składa się z 3 parametrów: GCD r a b oznacza: reszta z dzielenia(r), pierwszej liczby(a) z drugą(b),
  • i wreszcie ~ Four - odpowiednik = w typach, innymi słowy GCD 0 a 4 = 4 czyli największy wspólny dzielnik liczby a i 4 to 4.

Liczby naturalne

Zaczniemy od zdefiniowania liczb naturalnych:



Zero zaskakująco oznacza 0. Natomiast Succ n oznacza kolejną liczbę po n czyli Succ Zero oznacza 1. Ciężko byłoby nam definiować większe liczby więc dla wygody zdefiniowane są dodatkowe typy jak One, Two, Four itd.

Klasa Nat reprezentuje liczby naturalne i zawiera funkcję toInt konwertującą liczbę naturalną do Inta. Godna uwagi jest implementacja toInt dla Succ n. Otóż wynika ona z tego, że nie posiadamy konstruktorów ani typu Zero ani też Succ n. Wykorzystujemy więc specjalną zmienną undefined, która może udawać dowolny typ i rzutujemy ją na typ n wewnątrz Succ, który musi być klasy Nat. Aby w ogóle móc w ten sposób zaimplementować tę funkcję potrzebne jest rozszerzenie ScopedTypeVariables[3], czyli najlepiej dodać sobie w pliku taką linijkę:

{-# LANGUAGE ScopedTypeVariables #-}

Możemy przetestować sobie w ghci to co do tej pory napisaliśmy:

> toInt (undefined::Four)
4

Type Families

Przyszła pora na zdefiniowanie naszej funkcji liczącej GCD. Funkcja będzie oczywiście realizowana na poziomie typów danych i do tego celu użyjemy rozszerzenia TypeFamilies więc od razu dodajmy sobie na początek pliku:

{-# LANGUAGE TypeFamilies #-}

Przejdźmy zatem do kodu:

Jeżeli ktoś nie widział nigdy wcześniej tego rozszerzenia to może go zaskoczyć słowo instance. To słowo kluczowe przede wszystkim sugeruje, że rodziny typów są otwarte tak samo jak klasy. Oznacza to, że ich "instancje" mogą być rozrzucone po plikach i zawsze można dołożyć kolejne. W przeciwieństwie chociażby do funkcji, która w całości musi być zdefiniowana w jednym pliku.

I co my tu mamy? Rekursywnie zaimplementowany algorytm liczenia największego wspólnego dzielnika. Mamy 3 warunki brzegowe:

  • gdy reszta z dzielenia to 0 i pierwszy argument to 0 to GCD Zero Zero a = a,
  • gdy reszta z dzielenia to 0 i drugi argument to 0 to GCD Zero a Zero = a
  • gdy dwie liczby zeszły do zera to GCD r Zero Zero = r,

No to sprawdźmy to od razu w GHCi:

> toInt (undefined::(GCD Zero Eight Four))
4

> toInt (undefined::(GCD Zero Eight Two))
2

Treść funkcji

Pozostało nam napisać testową funkcję, która przyjmuje tylko liczby podzielne przez 4 po czym je wypisuje. Wykorzystując typ funkcji z początku postu możemy napisać na przykład coś takiego:

fun :: (Nat a, GCD Zero a Four ~ Four) => a -> Int
fun a = toInt a

Spróbujmy zatem wywołać ja w GHCi:

> fun (undefined::Eight)
8

> fun (undefined::Six)
<interactive>:2:1:
    Couldn't match type `Zero' with `Two'
    Expected type: Four
      Actual type: GCD Zero Six Four
    In the expression: fun (undefined :: Six)
    In an equation for `it': it = fun (undefined :: Six)

Jak widać funkcja nie może zostać wywołana dla szóstki, gdyż nie jest podzielna przez 4 ale dla ósemki działa wyśmienicie.

Kiedy można to wykorzystać

Nie wykorzystamy tego na pewno dla argumentów podawanych dynamicznie, tj. z zewnętrznych źródeł jak baza danych, plik, klawiatura itd. Kompilator w żaden sposób nie sprawdzi czy liczba, którą wpiszemy podczas uruchamiania programu jest podzielna przez 4. No bo niby skąd on ma wiedzieć co wpiszemy?

Ale możemy to wykorzystać przy argumentach podawanych na sztywno w kodzie źródłowym. Przypuśćmy, że mamy dwie funkcje. Jedna która działa na wszystkich liczbach i druga, która działa na podzielnych przez 4. Oczywiście argumenty przekazujemy na sztywno. Mamy dwa wyjścia, albo napisać dwa różne typy danych, tzn. liczby podzielne przez 4 i dowolne liczby, albo utworzyć tylko jeden typ danych tak jak w naszym przykładzie i użyć rodzin typów by móc wymusić na nich własności, których oczekujemy.

Podsumowanie

Haskell po raz kolejny wprowadza programowanie na wyższy poziom. Być może rodziny typów to nie jest rzecz, którą da się często wykorzystać (przynajmniej jeśli chodzi o funkcje na typach danych) ale można w ten sposób poradzić sobie z kilkoma problemami. Więcej o rodzinach typów można znaleźć tutaj. Pełen kod użyty w tym poście do podglądu tutaj.