sobota, 24 października 2020

O aborcji

W ostatnim czasie zrobiło się głośno na temat aborcji. Nie będę tu się wypowiadał na temat prawa jako takiego, gdyż specjalnie mnie ono nie interesuje - przynajmniej dopóki nie zmusza ludzi do robienia czegoś złego. Poruszyły mnie natomiast bardzo mocno reakcje moich znajomych i najbliższych, którzy przy okazji całej tej sprawy zdają się nie widzieć niczego złego w aborcji (czasami tylko w niektórych okolicznościach, a czasami bez względu na okoliczności).

Oto argumenty, które słyszałem:
1. Aborcja to wybór kobiety bo to jej ciało.
2. Aborcja to ukrócenie cierpienia matki.
3. Aborcja to ukrócenie cierpienia dziecka.
4. Aborcja do pewnego momentu to nie morderstwo bo płód to nie człowiek.

Zmierzę się najpierw z ostatnim argumentem, gdyż pozwoli mi to postawić fundamenty pod resztę obiekcji. Zacznijmy od tego czy argument w ogóle ma sens. Czy zabijanie jest czymś złym jeśli nie dotyczy człowieka?

Myślę, że łatwo można by tu przytoczyć wielorakie przykłady, że nie ma w tym nic złego. Ostatecznie zabijanie zwierząt lub roślin, choćby na pokarm wydaje się być nieuniknione i próżno szukać wskazówek co do zakazu ich zabijaia w Biblii. Jeśli więc płód nie jest człowiekiem argument zdaje się mieć sens. A na jakiej podstawie możemy to rozstrzygnąć?

Po pierwsze płód ma DNA człowieka. Absurdem byłoby szukać innego organizmu, który na pewno nie jest człowiekiem, a który miałby takie samo DNA.
Po drugie z płodu na pewno powstaje człowiek i jeśli by przyjąć, że płód nie jest w pełni rozwiniętym człowiekiem to to samo trzeba by przyjąć o noworodkach. Czy jest ktoś, kto dopuszczałby zabijanie noworodków? A jednak zdaje się, że z logicznego punktu widzenia między tymi dwoma przypadkami nie ma różnicy. Czy naprawdę o tym czy można zabić dziecko miałoby decydować jego położenie geograficzne?
Po trzecie by mówić o morderstwie trzeba mówić o życiu. Z pewnością płód jest żywy, inaczej nie mógłby się rozwijać. Martwe rzeczy nie rosną.
Po czwarte jeśli by przyjąć, że nie da się tego rozstrzygnąć to i tak lepiej jest traktować płód jako człowieka tak na wszelki wypadek. Jeśli nie jest człowiek to pozwalając mu żyć nie czynimy nic złego. W drugą stronę, jeśli jest to człowiek, a odbieramy mu życie sytuacja jest znacznie gorsza.

Racjonalnie więc trzeba przyjąć, że płód jest człowiekiem, a zatem w dalszej części będę aborcję nazywał prawem do zabijania.

Wybór kobiety, a jej ciało

Czy kobieta ma prawo zabić dziecko będące w jej łonie? Tutaj zdaje się być użyty argument z prawa własności do swojego ciała. Jednak dlaczego ciało miało by być jakimś wyjątkowym przypadkiem prawa własności? Jeśli więc posiadam dom, a w nim swoje dzieci to czy idąc tą samą logiką mam prawo zabić swoje dzieci jeśli ich tam nie chcę? Nie sądzę by ktokolwiek o zdrowych zmysłach postulował coś takiego. Prawo do życia jest nadrzędne nad prawem własności.

Ukrócenie cierpienia

Pozostałe dwa argumenty dotyczą w zasadzie tego samego. Czy cierpienie jest wystarczającym powodem do zabijania? Emocjnolanie wydaje się być to zbyt ciężkie by patrzeć na śmierć swojego dziecka, na jego cierpienie, które tym samym powoduje cierpienie rodziców. W szczególności jeśli dziecko rodzi się z tak olbrzymimi wadami, że umiera w ciągu godzin lub dni. Nikt nie chce tego doświadczać.

Ale dlaczego ten przypadek miałby być inny od pozostałych? Co jeśli moje dwuletnie dziecko nagle zachoruje na śmiertelną chorobę, która je zabije w ciągu kilku dni w potwornych męczarniach? Czy wtedy też mogę je zabić od razu? Z pewnością moim moralnym obowiązkiem jest uczynić wszystko by cierpiało jak najmniej. Jednak to nie to samo co odbieranie życia. I kto niby ma ustalać jaki stopień cierpienia albo jaka długość życia jest wystarczająca by uzasadnić morderstwo? Czy jeśli dziecko może maksymalnie przeżyć 10 lat to można je zabić? A może tylko wtedy jeśli 10 miesięcy? A czemu nie 10 i pół miesiąca?

A co jeśli stracę dobrze płatną pracę i będę musiał pracować w dużo gorszych warunkach przez 16 godzin dziennie by wyżywić moje dzieci? Cierpiał będę niewyobrażalnie tak jak i one. Czy to też jest dobra wymówka do ich zabijania?

A co ze starszymi osobami, powiedzmy 90+? Po co cały ten ZUS, przecież oni i tak niedługo umrą i jestem pewien, że wiele z nich cierpi. Czy może lepiej byłoby im wszystkim ukrócić cierpienia? Nie musielibyśmy wtedy tyle na ich wszystkich płacić. Same korzyści, prawda?

Przykładów można by mnożyć i mnożyć. Jednak do tej pory skupiłem się tylko na tym co widać. Na tym też skupia się publika. Jednak jest wiele rzeczy, których nie widać. Wydawać by się mogło, że odebranie życia dziecku to sytuacja win-win: nie cierpi matka i nie cierpi dziecko. Chcę jednak zwrócić uwagę na to co jeszcze odbieramy wraz z życiem zwłaszcza w przypadku dziecka, które i tak nie przeżyje zbyt długo:
- pierwszy kontakt ze światem,
- widok i dotyk swojej matki,
- pierwsze odczucie miłości i opieki,
- możliwość pożegnania,
- możliwość odejścia wśród kochających je ludzi.
Czy to tak wiele by poświęcić kilka godzin, dni, lat oddając wszystko co tylko możemy dla swojego umierającego dziecka?

Reasumując nie widzę żadnej moralnej przesłanki by usprawiedliwić prawo rodziców do zabijania swoich dzieci zwane aborcją. Zamiast tego chciałbym by każdy z nas pomagał jak tylko potrafi osobom w swoim otoczeniu, które zmagają się z takimi problemami. Sam chciałbym móc uczynić więcej bo wiem, że równie łatwo odwracam swój wzrok. A jaka jest twoja opinia?

czwartek, 31 sierpnia 2017

Szczepienia czyli utylitaryzm wciąż ma się dobrze

Zazwyczaj mało interesują mnie dyskusje, którymi tak emocjonują się ludzie w szczególności w mediach społecznościowych. Czasami jednak po przeczytaniu, chcąc nie chcąc, kilku komentarzy ciężko opanować wzburzenie. Tyle, że zamiast toczyć niekończącą się wymianę zdań na facebooku wolę zebrać swoje przemyślenia w jednym miejscu. Nie będą one dotyczyć tego czy szczepienia działają czy nie, lecz samego przymusu czy też obowiązku szczepień.

Zacznę od tego, że niniejszy post skierowany jest do ludzi wierzących w obiektywne wartości moralne. Jeżeli jesteś relatywistą moralnym to nic z tego tekstu do ciebie nie trafi. Dla tych, którzy nie wiedzą o czym mowa, relatywista moralny to ktoś, kto na pytanie:

Czy dobrze jest molestować małe dzieci?

odpowiada:

Jeżeli sam tak uważasz to jak najbardziej!

Najczęściej takie osoby charakteryzują sformułowania typu "każdy ma swoją moralność", "dlaczego moja moralność ma być gorsza od twojej" itp. Osoba wierząca w obiektywne wartości moralne uważa, że bez znaczenia jest co kto myśli, ponieważ są one niezależne od ludzkiej opinii.

Czytając te wszystkie dyskusje nie trudno zauważyć, że głównie pojawiają się dwa rodzaje argumentów. Pierwszy brzmi mniej więcej tak:

Jestem za tym by rodzice mogli decydować o szczepieniu dziecka ale tylko wtedy jeśli pokryją koszty leczenia jak to dziecko zachoruje.

Jak rozumiem logika jest mniej więcej taka, że skoro służba zdrowia jest "darmowa", a ci rodzice jawnie postępują przeciwko jej zaleceniom to niech płacą za swoje błędy. Darmowa oczywiście nie jest bo wszyscy za nią płacą w ten czy inny sposób ale to niewiele zmienia. Możemy to rozumieć też tak, że dlaczego wszyscy teraz mają się składać na leczenie dziecka, którego rodzic celowo nie szczepił (czyli celowo ryzykował). Problem polega na tym, że chcąc zachować spójność to zgodnie z tą logiką płacić (dodatkowo) za swoje leczenie powinny również osoby:

  • otyłe, gdy zachorują na choroby układu krążenia,
  • palące papierosy, gdy np. dostaną raka,
  • pijące alkohol, gdy wątroba zacznie szwankować,
  • wszyscy pracownicy nie stosujący się do zasad BHP, gdy już coś im się stanie,
  • osoby nie dbające o higienę osobistą,
  • osoby z chorobami wenerycznymi,
  • itp. itd.

Na dobrą sprawę to właściwie każdy znajdzie coś dla siebie. Argument ten podyktowany jest zapewne poczuciem sprawiedliwości (dlaczego ja mam płacić za błędy kogoś innego), tyle, że jedyny sprawiedliwy układ to taki kiedy każdy sam płaci za swoje leczenie. Tak jak każdy sam płaci za swoje jedzenie czy swój samochód.

Druga opinia pojawia się najczęściej w odpowiedzi na tę pierwszą:

Przymus musi być bo są osoby, których zaszczepić nie można i zdrowie gwarantuje im tylko duża liczba zaszczepionych osób, które blokują rozprzestrzenianie się choroby.

Tu dochodzimy do kwestii moralnej. Otóż to co tu widzimy to nic innego jak utylitaryzm. To taki pogląd, w którym uważa się, że dobre jest to co generuje najwięcej szczęścia. W tym przypadku będzie to zdrowie jak największej liczby osób. Utylitaryzm najczęściej kończy się w sytuacji, gdzie szczęście wielu osób zależy od twojego nieszczęścia. Dla przykładu, w szpitalu leży pięć osób. Każdej z nich trzeba przeszczepić inny organ. I tak się składa, że jest tam także zdrowa osoba, której organy idealnie "pasowałyby" dla tych pięciu osób. Zgodnie z utylitaryzmem dobrze byłoby poświęcić jej życie by uratować pięć. I nie ma ona nic do gadania.

Utylitaryzm jest częścią większej grupy poglądów zwanej konsekwencjonalizmem czyli czy dany czyn jest dobry czy nie określają jego konsekwencje. A konsekwencją takiego myślenia są oczywiście wszelkie państwowe przymusy. Przypuszczam, że gdyby można było z duża dokładnością stwierdzić, że śmierć dziecka pogodzi ze sobą na powrót skłócone małżeństwo to by powstało prawo nakazujące takie dzieci zabijać.

Utylitaryzm nie ma nic wspólnego z moralnością. Zdaje się, że jest on podstawą socjalizmu jaki dzisiaj widzimy. Prowadzi on tylko i wyłącznie do pozbawiania wolności. Niestety ludzie dzielą się na tych, którzy chcą żyć w wolności i tych co chcą żyć bezpiecznie. Tych drugich jest znacznie więcej ale jak mawiał Benjamin Franklin: "ludzie, którzy dla tymczasowego bezpieczeństwa rezygnują z podstawowej wolności, nie zasługują ani na bezpieczeństwo, ani na wolność".

piątek, 21 lipca 2017

Elm - frontend, który po prostu działa

Od dłuższego czasu pracuję nad aplikacją InkassoTrade. Projekt jest już uruchomiony i udostępniony publicznie więc mogę opisać nieco technologiczne jego aspekty.

InkassoTrade

Aplikacja skierowana jest głównie do firm mających problemy ze ściąganiem należności. Umożliwia (całkowicie za darmo!) wysyłanie monitów mailowych przy użyciu nieprzeciętnie zaawansowanego mechanizmu reguł. Pozwala też ona sprawnie zarządzać fakturami i wpłatami, a monity można dowolnie dostosowywać do swoich potrzeb.

Już praktycznie na samym początku podjęliśmy decyzję by interfejs pisać w Elmie. Miałem wcześniej już sporo doświadczenia pisania w tym języku ale zdecydowanie jest to mój największy projekt. Mimo, że czasem nie było różowo to w żadnym frameworku JS typu React czy Angular nie byłbym w stanie ukończyć frontendu w takim czasie. Jednak funkcyjny język i to statycznie typowany pozwala uniknąć wielu problemów.

Garść technicznych informacji

Na początek parę informacji o rozmiarze projektu:

  • Liczba linii kodu: 21286
  • Liczba bibliotek zewnętrznych: 15
  • Liczba portów: 2
  • Liczba linii kodu portów: 425
  • Rozmiar skompilowanego kodu (zminimalizowany i spakowany): 99,6 KB

O Elmie

Dla osób nie znających Elma powyższe dane mogą być nieco niezrozumiałe. Szczególnie jeśli chodzi o porty. Elm oprócz bycia funkcyjnym i statycznie typowanym językiem umożliwia podobnie jak Haskell kontrolowanie efektów. Oznacza to, że operacje takie jak pobranie aktualnej daty, wysłanie żądania HTTP czy wykonanie zewnętrznego kodu JavaScript nie da się wykonać bezpośrednio. Odbywa się to w zupełnie innym trybie, przypomina to trochę programowanie asynchroniczne w node.js. W Elmie każda aplikacja dzieli się na trzy podstawowe części:

  1. model - struktura, w której trzymany jest stan aplikacji
  2. view - funkcja, która zwraca kod HTML na podstawie modelu
  3. update - funkcja, która na podstawie akcji/zdarzenia i aktualnego modelu zwraca nowy model (aktualizuje go)

Dla przykładu powiedzmy, że mamy model, który zawiera liczbę kliknięć w jakiś przycisk. Mamy też widok, który rysuje go oraz obok wyświetla licznik kliknięć. Widok oprócz generowania samego kodu HTML "nazywa" także akcję, która ma się wykonać po kliknięciu. Natomiast funkcja update reaguje na daną akcję zwiększając licznik w modelu. Poniższy diagram sekwencji przedstawia ogólną zasadę działania:


Obrazek pochodzi ze strony elmprogramming.com, którą polecam przejrzeć podczas nauki tego języka.

Jeżeli czegoś nie można w danej chwili zrobić w Elmie to zawsze można wykorzystać do tego JavaScript. Taki kod może przesyłać dane do aplikacji przez tzw. port lub może odbierać przez niego dane wysyłane przez aplikację. Jak na takiej wielkości projekt to liczba portów jest niewielka. Wychodzi więc na to, że bardzo dużo rzeczy da się zrobić bezpośrednio w Elmie.

Dobre strony

Zacznę od tych dobrych stron, gdyż jest ich znacznie więcej. Oczywiście większość z nich wynika z samej natury funkcyjnych i statycznie typowanych języków. Jednakże Elm jest unikatowy w paru kwestiach:

  1. Wbudowany debugger - z racji tego, że wszystkie efekty wykonywane są spoza kodu aplikacji możemy ich historię wywołań przeglądać we wbudowanym debuggerze. Wystarczy tylko skompilować kod z opcją --debug i możemy przeglądać jak zmieniał się model aplikacji po każdej akcji. Dodatkowo tester, gdy napotka błąd może taką historię z debuggera wyeksportować i wysłać programiście. Programista wówczas ma ułatwione zadanie bo widzi co dokładnie tester robił i jak zachowywała się aplikacja.
  2. Virtual DOM - nie jest to nic specyficznego tylko dla Elma. Vritual DOM wykorzystuje także np. React ale dzięki niemu Elm jest naprawdę szybki. Nie skupialiśmy się w tej chwili w ogóle na wydajności interfejsu i pisaliśmy go w najprostszy możliwy sposób. Mimo to działa on całkiem sprawnie. Jest to doświadczenie dokładnie odwrotne do tego, które miałem przy pisaniu interfejsów w React'cie czy w ClojureScript'cie.
  3. Refactoring - kod tych rozmiarów co jakiś czas musiał przechodzić refactoring. Jest to normalne, gdyż z czasem jak nasza aplikacja rośnie widać coraz więcej schematów w kodzie, które można uogólnić. Kompilator Elma jest tu nieodłącznym przyjacielem programisty. Dzięki niemu nie tylko nie zapomnimy o żadnym miejscu do poprawy ale przede wszystkim komunikaty jakie zwraca są bardzo przyjemne.
  4. Separacja kodu - każdy programista webowy dobrze zna model MVC. W Elmie jest to oczywiście daleko bardziej posunięte ale przede wszystkim wymuszone. Programista nie może zmienić modelu w widoku, ani też zmienić widoku w funkcji update. Pozwala to uniknąć wielu problemów oraz kod wydaje się być bardziej przejrzysty.
  5. Formatowanie kodu - w tej kwestii Elm przypomina język Go. Dostępne jest świetne narzędzie o nazwie elm-format, które formatuje kod za ciebie. Jest to bardzo wygodne, gdy nie trzeba się zastanawiać ani później poprawiać formatowania.

Mniej przyjemne strony

Pisząć w Elmie natknęliśmy się również na parę trudności. By być uczciwym opiszę je pokrótce. Nie wpłynęły one specjalnie na całokształt projektu ale gdyby ich nie było to oczywiście zakończyłby się on szybciej:

  1. Brak wsparcia dla wysyłania plików - jest to znany problemy i programiści obchodzą go na różne sposoby. Elm potrafi przesyłać dowolne dane oprócz właśnie danych binarnych. Niektórzy obchodzą to przez kodowanie danych w base64 ale wymaga to również obsługi po stronie backendu. My zamiast tego rozwiązania postanowiliśmy wysyłać formularz w tle jako mulitpart/form-data. Tak czy siak obsługa plików wymaga napisania portu w JavaScript'cie, który się tym zajmie.
  2. Widgety/komponenty - najgorętszy temat na wszystkich forach i grupach dyskusyjnych dot. Elma. Nie ma jednej metody na ich definiowanie. Ba, nawet nie jest zalecane by wszystkie widgety tworzyć "na jedno kopyto". To nie jest jednak największy problem. Jest nim brak bibliotek z widgetami. Praktycznie każdą bardziej zaawansowaną tabelkę (z sortowaniem czy filtrowaniem) trzeba pisać od zera. Nie jest to oczywiście dużo roboty ale trzeba na nią poświęcić czas.
  3. Czas kompilacji - jak plików źródłowych przybywa czas potrafi się nieco wydłużyć. Szczególnie jak zmienimy plik, od którego zależą wszystkie moduły. Nie jest to jakoś szczególnie długi czas, w naszym przypadku kompilowanie kodu po raz pierwszy trwa ok. 1 minuty. Podobno planowana nowa wersja Elma ma ten proces znacząco przyspieszyć.
  4. Metaprogramowanie - przy coraz większym kodzie bardzo często chcielibyśmy aby pewne rzeczy działy się automatycznie. Dla przykładu, gdy mamy zdefiniowany typ menu, który może być stroną główną, listą faktur lub listą wpłat to bardzo często definiujemy sobie listę wszystkich możliwych kombinacji. Niestety Elm bez możliwości generowania kodu nie zadba o to by przy każdym dodaniu nowego menu funkcja zwracająca listę wszystkich menu się sama aktualizowała. Kompilator również tutaj nie pomoże w żaden sposób (np. nie wyświetli ostrzeżenia).

To wszystkie problemy, o których pamiętam. Zapewne większość z nich zostanie poprawiona w kolejnych wydaniach języka. Nie są to też problemy, które stanową blokadę przed używaniem tej technologii. Myślę, że zalety są na tyle duże, że nie można przejść obok Elma obojętnie. Jeśli w najbliższym czasie planujesz pisać aplikację webową to daj mu szansę.

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.