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.

poniedziałek, 3 czerwca 2013

Okradać czy nie okradać

Ostatnio pisałem o problemach jakie niesie ze sobą wspieranie konsumpcji przez rząd (oraz o błędach w rozumowaniu). Dzisiaj natomiast postaram się wykazać logiczne następstwa działań rządów wspierających konsumpcję oraz dla porównania, tych wspierających produkcję. Zdradzę od razu, że słynne "kto chce obniżać podatki w czasie kryzysu ten kłamie" jest całkowicie logiczne lecz, jak już pisałem, oparte na błędnych założeniach. Nic co opiera się na kradzieży nie może przecież prowadzić do wszechobecnego dobra, gdyż wówczas kradzież musiałaby nim również być.

Wspieranie konsumpcji

Rząd wierzący ślepo w konsumpcję musi się zastanowić w jaki sposób może zachęcić czy wręcz zmusić obywateli do większej konsumpcji. Może to zrobić na wiele sposobów ale postaram się pokazać te najbardziej powszechne.

1. Kontrola pieniądza

Przede wszystkim ludzie zaczną więcej konsumować jeżeli nie będzie opłacało się oszczędzać. Stanie się tak np. wtedy, gdy każda złotówka, którą ludzie zarobią po jakimś czasie zmniejszy swoją wartość. Na przykład, jeżeli zarobię dziś 100 zł to jedyne co mnie powstrzyma przed trzymaniem ich w skarpecie to fakt, że za miesiąc kupię za nie tyle co dzisiaj za 90 zł. Czyli gdy ze 100 zł zrobi się 90. Ale jak to osiągnąć? Sprawa jest prosta. Wystarczy, że zwiększy się podaż pieniądza, gdyż wartość pieniądza, tak jak każdego dobra na rynku, kształtuje podaż i popyt. Jak wiadomo gdy rośnie podaż to spadają ceny. W tym celu tworzymy narodowy bank centralny, który będzie posiadał maszyny drukarskie aby móc fizycznie zwiększać podaż pieniądza zgodnie z wolą rządu. Jednak aby tego dokonać musimy jeszcze zadbać o to by w całym kraju można było płacić tylko tym pieniądzem emitowanym przez bank centralny. Jeżeli rząd by tego nie dokonał to ludzie widząc, że papier nie trzyma wartości woleliby płacić czymś, np. srebrem, złotem (czy nawet papierosami).

Czy to wystarczy? Jak już pisałem poprzednio, rozdanie ludziom po prostu dodatkowych pieniędzy spowoduje nagły wzrost cen więc skończyłoby się to tym, że ludzie wszystkie zarobione pieniądze wydawaliby na najbardziej przyziemne potrzeby. Cóż więc można zrobić? Należy stopniowo rozdawać pieniądze i to konkretnym małym grupom ludzi, którzy wykorzystają je na ważne, w mniemaniu rządu oczywiście, inwestycje.

2. Kontrola stóp procentowych

Rząd może rozdawać pieniądze ale niezbyt często i tylko niektórym podmiotom. To może nie wystarczyć (a kiedy wreszcie wystarczy?) więc należy zadbać o to, by również zwykli zjadacze chleba mogli kupować dobra wyższego rzędu bez potrzeby oszczędzania (gdyż podczas oszczędzania gospodarka się nie rozwija). W jaki inny sposób niż oszczędzanie można coś kupić na co się nie ma pieniędzy? Oczywiście biorąc kredyt. Jak wiemy banki nie chętnie rozdają kredyty osobom, które nie mogą ich spłacić. Władza musi więc zadbać o dwie rzeczy. Po pierwsze musi zmusić banki do rozdawania kredytów po określonym oprocentowaniu oraz aby tego dokonać musi im zagwarantować, że nie upadną.

Z czego banki udzielają kredytów? Uczciwie? Z pieniędzy, które wpłacają klienci i, co do których udzielają zgody na pożyczanie ich przez bank w zamian za część zysków. Niestety w ten sposób nie powstaje za wiele kredytów. Idealnie byłoby tak sprawić aby każdy pieniądz wpłacony do banku mógł być pożyczany. Jednakże wówczas wystarczy, że klienci nagle zechcą wypłacić wszystkie swoje pieniądze (tzw. run na bank), a bank nie będzie miał po prostu z czego oddać. Taki system nazywa się ładnie systemem o rezerwie cząstkowej, a mówiąc prościej piramidą finansową, w której to tylko część wpłaconych pieniędzy pozostaje w banku. Ta część nie może pozostać bez kontroli (w Polsce wynosi 3,5%). Ale jak zabezpieczyć banki przed ich upadkiem w przypadku runu? Nie można dopuścić by bankowi zabrakło pieniędzy więc musi powstać odpowiedni fundusz, na który będą się zrzucały wszystkie banki (czyli de facto ich klienci) i w razie czego będzie można tym je uratować. Jeżeli to nie wystarczy to są przecież jeszcze maszyny drukarskie. Oprocentowanie kredytów kontroluje się za pomocą tzw. stóp procentowych czyli minimalnego oprocentowania kredytu, po którym bank udziela go swoim najlepszym klientom.

Warty odnotowania jest fakt, że jeżeli bank może pożyczać 96,5% wpłaconych pieniędzy to będzie chciał pożyczać je jak najtaniej bo przy takiej ilości środków najmniejszy procent będzie generował horrendalny zysk, a im mniejszy procent tym więcej pieniędzy uda się pożyczyć. Jak wiemy tanie kredyty spowodują natychmiastowy wzrost cen tak samo jak dodrukowanie pieniędzy (gdyż jest to właściwie to samo, ale to tym kiedy indziej). Co jest kryterium ustalania stóp procentowych? Oczywiście skala konsumpcji. Jeżeli eksperci uznają, że ludzie dużo konsumują to można te stopy utrzymywać na wyższym poziomie, a jeżeli za mało to trzeba je obniżyć. Oczywiście najlepiej tą odpowiedzialność zrzucić "na ekspertów" więc stopy procentowe w Polsce ustalają ekonomiści wchodzący w skład Rady Polityki Pieniężnej.

3. Podatki

Każdy rząd jest świadomy tego, że ludzie zazwyczaj są głupi i konsumują to co służy im egoistycznym pragnieniom. Ale przecież ktoś musi zadbać o wyższe cele takie jak badania naukowe, ochrona środowiska, utrzymywanie pokoju na świecie czy edukacji i służba zdrowia. Na te cele potrzeba by dodrukować sporo pieniędzy, a to odbiłoby się zbyt gwałtownym wzrostem cen. Co można innego zrobić? Oczywiście ukraść... tfu, opodatkować! Innymi słowy jak zabierze się ludziom pieniądze to ich przecież nie przybędzie i będzie można je wydać na rzeczy, na które oni nie chcą sami wydawać. W ten sposób rząd uchroni kluczowe dla niego firmy przed upadkiem, a nawet sprawi, że one nigdy nie będą musiały się troszczyć o klientów, gdyż pieniądze zawsze się dla nich znajdą. Czyli podatki utrzymuje się możliwie wysokie (aby mogły się rozwijać strategiczne dla państwa firmy). Szczególnie ważne jest to w czasie kryzysu kiedy ludzie, gdy czują, że ich posady są zagrożone, zaczynają szczególnie oszczędzać.

Rząd doskonale wie, że ludzie nie lubią płacić podatków i będą szukali schronienia czy to w innych krajach czy to ukrywając swoje dochody. Prawo zatem musi być skonstruowane tak aby takie działania były szczególnie piętnowane. Jednak aby wymusić stosowanie prawa, potrzebna jest kontrola policji i sądów. To rzecz najzupełniej oczywista. Ale samo zastraszenie ludzi nie wystarczy. Najlepiej byłoby sprawić by ludzie uznali podatki za coś dobrego, coś z czego czują, że odnoszą korzyści. Można oczywiście wybudować jakieś drogi, parki, rozdać trochę pieniędzy ubogim (tylko nie za dużo aby zostało coś na kluczowe firmy) ale to i tak nie wystarczy. Te barany zawsze liczą pieniądze i jak będą mieli przynieść połowę pensji do urzędu to oczywiście nikt nie przyjdzie, a to by oznaczało użycie siły. Jest lepszy sposób. Jak wiadomo ludzi, którzy dają pracę jest zdecydowanie mniej więc ich łatwiej będzie zastraszyć. Wystarczy aby to oni odprowadzali podatki za swoich pracowników. Czyli pracownik z pensji, którą otrzyma od pracodawcy już nic nie będzie musiał nic oddać. Ba, mało tego, jak rząd wprowadzi jeszcze ulgi podatkowe to będzie on (pracownik) miał wrażenie, że nie tylko nic nie płaci ale to jemu państwo dopłaca!

4. Kontrola konkurencji

Rozumiemy już, że szczególnie złym dla państwa zjawiskiem są zbyt szybko obniżające się ceny. Oczywiście zbyt szybko podnoszące się ceny też będą złe, gdyż nikt nie zdąży na nie zarobić, a ratowanie później gospodarki tanimi kredytami może się źle skończyć. Obniżające się ceny mogą skłonić ludzi do zwlekania z kupnem towaru, a więc do ograniczenia konsumpcji. Co jest oczywiście niedopuszczalne! Jak z tym walczyć? Jednym z czynników powodujących spadek cen jest rosnąca konkurencja. Z jednej strony konkurencyjne firmy mogą się dogadać między sobą (tzw. zmowa cenowa), a z drugiej może znaleźć się ktoś zbyt uzdolniony, kto wykona tę samą pracę połowę taniej. A co będzie jeśli po nim znowu znajdzie się ktoś kto wykona to jeszcze połowę taniej? Nie możemy do tego dopuścić. Najlepszy jest oczywiście złoty środek czyli ceny wśród konkurentów powinny oscylować wokół średniej. Gdy któraś firma wypada zbyt daleko od średniej (oczywiście gdy produkuje taniej, bo gdy drożej to i tak nikt tego nie kupi) to znak, że stosuje dumping cenowy (produkuje za tanio) co jest bardzo złe dla gospodarki. Przez nią mogą upaść pozostałe firmy produkujące drożej czyli znowu spadłaby konsumpcja. Tak jak w przypadku podatków rząd musi się skupiać na piętnowaniu firm, gdyż bez ich kontroli konsumpcja może całkowicie zaniknąć.

5. Inne regulacje

Jest jeszcze wiele innych zagadnień. Na przykład trzeba jakoś sprawić by z jednej strony firmy płaciły coraz więcej pracownikom by mogli coraz więcej konsumować (np. płaca minimalna, związki zawodowe), co znowu jest podwójnie dobre, gdyż pozbawia firmę tak złych oszczędności. Jak firma nie chce wydawać to niech zrobi to jej pracownik. A jeśli firma nie ma oszczędności? To trzeba ją poratować jakąś dotacją, sprawiając tym samym, że zaczyna ona dbać wówczas o to czego chce państwo, a nie o to czego chcą klienci. Państwo płaci, państwo wymaga. Z drugiej strony ważne jest by obywatel zawsze liczył na państwo, gdyż wtedy nie będzie próbował omijać podatków.

Podsumowanie

Jak widać niemal wszystkie działania rządu są całkowicie logicznym następstwem przesłanki, że wspieranie konsumpcji prowadzi do dobrobytu. Problem oczywiście w tym, że ta przesłanka jest błędna. Dla porównania zobaczmy co robiłby rząd, który wspiera (całkiem słusznie) produkcję.

Wspieranie produkcji

Czy rząd potrzebuje kontroli pieniądza do motywowania ludzi do działania? Dodrukowywać nie ma sensu, ponieważ w ten sposób pozbawia ludzi oszczędności, które są odkładane na inwestycje (czyli konsumpcję odłożoną w czasie). Jeżeli zabierze wszystkim tyle samo to nic to nie zmieni, gdyż zarówno ludzie będą mieli mniej pieniędzy jak i ceny spadną. Jeżeli zabierzemy niektórym to nie odłożą oni na inwestycje. Zatem kontrolowanie pieniądza nie ma sensu.

A stopy procentowe? Nie jest w interesie rządu narzucać rynkowi ceny kredytów, gdyż jeżeli nie trafi to znowu zabierze ludziom oszczędności lub zabierze ludziom kredyty, które w normalnej sytuacji nie są szkodliwe (tzn. wtedy gdy klient wie, że jego oszczędności są pożyczane i może je stracić), a nawet pożyteczne. Pożyteczne dlatego, że jeżeli ryzykant biorący kredyt wytworzy tyle dobra, że nie tylko go spłaci ale jeszcze coś dołoży to tylko możemy się cieszyć. Ale dlaczego nie są szkodliwe? Gdyż saldo się zgadza. Jak bank pożyczył pieniądze to mamy 0 na koncie, a nie jak poprzednio tyle samo co mieliśmy (czyli nie przybyło pieniądza do gospodarki). Jak bank będzie oszukiwał to rynek doprowadzi go w końcu do upadku. Jak klient nie spłaci to nic się nie stanie, bo klient banku liczył się z ryzykiem. Po prostu klient nie zaufa drugi raz temu bankowi, a bank następnym razem będzie bardziej uważny komu pożycza pieniądze.

Podatki? Każde opodatkowanie zmniejsza oszczędności czyli rząd musi starać się je utrzymywać możliwie najniższe (lub wcale).

Kontrola konkurencji? Rynek świetnie ją kontroluje. Firmy produkujące za drogo lub nieuczciwie są przez klientów omijane więc upadają. Nie ma kto ich ratować. Zatem wszystko pod kontrolą :)

Co innego można zrobić? Przede wszystkim musimy dbać o prawo własności oraz o rozwiązywanie konfliktów. Każda kradzież działa tak samo jak podatki (właściwie to to samo, nie?). Oczywiście można pozostawić tę sprawę rynkowi, a można ją też znacjonalizować. Nie wchodźmy tu wspór co jest lepsze.

Streszczenie

Poniżej to samo, w krótkiej formie:

WSPIERANIE KONSUMPCJIWSPIERANIE PRODUKCJI
kontrola pieniądza - jedna państwowa papierowa walutabrak kontroli pieniądza, wiele pieniędzy na rynku (w tym brak papierowych)
kontrola banków i stóp procentowychbrak kontroli banków i stóp procentowych
możliwie wysokie podatkimożliwie niskie podatki (lub ich brak)
kontrola konkurencji, dużo regulacji firmbrak kontroli, mało regulacji (lub w ogóle)
płaca minimalna, związki zawodowe, dotacje, socjal (by każdy mógł coraz więcej konsumować)oczywiście brak ingerencji państwa w życie obywateli, każdy jest odpowiedzialny za siebie


Podsumowując, wspieranie konsumpcji sprowadza się przede wszystkim do okradania obywateli. Ludzi traktuje się jak bydło, które nie wie co dla nich dobre. I to, jak mówią, dla naszego własnego dobra. Tym bardziej nie ma to sensu, gdy już wiemy, że to podaż tworzy popyt, a nie odwrotnie. Całe zagadnienie sprowadza się do tytułowego pytania okradać czy nie okradać.

Polecam bardzo książkę "Historia pisana pieniądzem" Jakuba Wozinskiego, która ukazuje losy Polski w kontekście pieniądza. Jak się zapewne spodziewasz każdy władca chciał mieć monetę w swoim ręku i zmuszał ludzi do posługiwania się nią. W ten sposób mógł bezkarnie okradać obywateli finansując swoje cele. Nic od tamtej pory niestety się nie zmieniło.

środa, 29 maja 2013

Czy konsumpcja jest dobra?

Dzisiaj piszę w oderwaniu od programowania, gdyż z pięknego snu od czasu do czasu wyrywa mnie ludzka ignorancja - tym razem ekonomiczna. Do programowania na pewno jeszcze powrócę lecz teraz czas wyjąć skrywaną broń i poruszyć sprawy największej wagi.

Otóż wiele naprawdę cenionych w Polsce osób twierdzi, że dzięki konsumpcji gospodarka się rozwija. Logicznym następstwem tej teorii jest negowanie posiadania oszczędności, gdyż jak to ujął pewien klasyk "pieniądze te raczej marnowałyby się na lokatach niż realnie pracowały w gospodarce". Skąd ten pomysł? Ano stąd, że jeśli kupujemy np. lizaka to firma, która go wyprodukowała dostaje za niego zapłatę, a więc sygnał, że lizak jest ludziom potrzebny i zaczyna produkować więcej. Zatem, jak twierdzą nasi bohaterowie, konsumpcja napędza produkcję. Im więcej kupujemy tym więcej będzie wytwarzane, czyli będzie więcej dóbr na rynku, a więc gospodarka się będzie rozwijać. Prawda, że proste?

Ale zaraz, zaraz. Dlaczego więc nie zmusić ludzi do wydawania wszystkiego co zarobią, a może nawet i jeszcze więcej? Przecież więcej konsumpcji = więcej dóbr = większy wzrost gospodarczy, czyż nie? Taki stan jest łatwo przecież osiągnąć (dla rządu), wystarczy tylko obniżyć stopy procentowe (do 0 albo jeszcze niżej) co spowoduje, że kredyty będą tanie jak barszcz. Wówczas lepiej będzie wziąć kredyt na powiedzmy milion złotych (bo będziemy musieli oddać tylko milion albo jeszcze mniej) niż oszczędzać mozolnie przez 20 lat.

No to mamy milion właściwie za darmo więc co? Kupujemy jacht? A co! Idziemy zatem do producenta i patrzymy, a tam ceny za jacht wynoszą nie milion, a 100 milionów. Ale jak to? Nie tylko my wzięliśmy ten kredyt ale wiele innych osób również skorzystało z okazji przed nami. Producent więc widząc gwałtownie rosnący popyt podwyższył cenę.

No to co, może chociaż mieszkanie? Powiedzmy, że jesteśmy jednymi z pierwszych, którzy chcą je kupić ale i tak teraz cena małego mieszkania wzrosła do miliona. Nie no miliona za małe mieszkanie to przecież nie damy! Nawet jeśli byśmy dali to i tak następni nie kupią go już po tej cenie. To przynajmniej zabawimy się trochę. Kupimy sobie trochę lodów, pójdziemy do kina i na łyżwy. Ceny tych dóbr również wzrosły (bo wzrósł popyt) ale nie aż tak bardzo więc za milion to trochę sobie poużywamy.

Widzimy więc, że większość konsumpcji zostaje ulokowana w bardzo przyziemnych dobrach. Skoro tak to napędziliśmy gospodarkę. Niestety nikt nie zainwestowałby tych pieniędzy w przemysł (np. kupując samochód, komputer, samolot czy płacąc za autostradę) bo by się nie opłacało (cena byłaby zbyt wysoka). Koniec końców nikt by tych kredytów już nie chciał brać, poniewż ceny rosłyby z godziny na godzinę. Ludzie w końcu przestaliby konsumować cokolwiek, gdyż aby to uczynić to wszystko co zarobią danego dnia musieliby migiem od razu wydać. Najlepszym pomysłem w takiej sytuacji byłoby po prostu samemu prowadzić gospodarstwo i zadbać sobie o jedzenie, wodę i wszystkie podstawowe potrzeby niż chodzić do pracy po pensję, którą moglibyśmy nie zdążyć skonsumować. Czyli w efekcie gospodarka zmieniła kierunek w produkcję przyziemnych dóbr po czym by padła, gdyż ludzie wrócili do własnych gospodarstw i przestali cokolwiek kupować.

Czyli co, konsumpcja jest zła? Zastanówmy się jak to wygląda, gdy rząd nie ingeruje w gospodarkę w ogóle. Naszym marzeniem jest jacht za milion złotych. Nie mamy takich pieniędzy i nie ma tanich kredytów. Pozostaje nam oszczędzanie i inwestowanie tych oszczędności. Ale skąd wziąć te oszczędności? Najpierw musimy coś wytworzyć co będzie wartościowe dla innych ludzi. Czy to w postaci naprawienia komuś kranu czy wyprodukowania samochodu - nie ma to większego znaczenia, co najwyżej uzyskamy mniej środków w zamian za pierwszą opcję. Później musimy ograniczyć konsumpcję aby nie wydać wszystkiego. Po jakimś czasie uzyskujemy odpowiednie oszczędności i możemy kupić jacht. Po prostu odłożyliśmy swoją konsumpcję w czasie aby kupić coś bardziej dla nas wartościowego. Wyszło więc na to, że produkując (np. naprawiając kran) wytworzyliśmy popyt (na jacht za milion).

No dobrze ale co jeśli wszyscy zaczną kupować lody za zaoszczędzone pieniądze? Gdyby tak się stało to wynikałoby to z faktycznych potrzeb tych ludzi w przeciwieństwie do poprzedniego przykładu gdzie po prostu nic innego nie dało się kupić. W ten sposób nie ma straty dla gospodarki, ponieważ rozwijamy te gałęzie gospodarki, które naprawdę są ludziom potrzebne.

Zatem twierdzę, że to produkcja prowadzi do dobrobytu, a nie konsumpcja. Odpowiadając na tytułowe pytanie to konsumpcja jest dobra o ile jest wydawana na towary, które w danej cenie faktycznie ludzie potrzebują. Innymi słowy konsumpcja jest dobra dla całej gospodarki jeżeli jest dobra dla jednostki. Nietrafione inwestycje powodują w dalszym czasie kryzysy (upadek firm inwestujących nietrafnie tworzy bezrobocie). Dobrym tego przykładem jest ostatni kryzys na rynku nieruchomości. Najpierw rząd (USA) powiedział bankom aby dawały kredyty każdemu kto chce kupić mieszkanie (nie zależnie czy będzie potrafił go spłacić). W razie nie spłacenia kredytu przez kredytobiorcę, miał go spłacić rząd. No to sielanka trwała, ceny nieruchomości nieustannie rosły (bo zwiększał się popyt), firmy budowlane rosły razem z nimi aż nagle rząd stwierdził, że nie będzie dalej spłacał dłużników. Co się stało? Zaczęły padać banki, firmy budowlane, a razem z nimi firmy produkujące dla tych firm oraz klienci tych banków. Cele ludzi się nie zmieniły (nadal chcą mieć własne mieszkanie), a mimo to firmy upadły. W przypadku oszczędności nie ma tego problemu bo jeśli chcemy mieć mieszkanie to na nie oszczędzamy czyli przede wszystkim skupiamy się na produkcji. Nikt nam nie zakręci kurka. Taki kryzys może wywołać jedynie masowa kradzież naszych oszczędności (Wielki Kryzys w Ameryce). Oczywiście czasami ludzie dochodzą do wniosku, że wytworzone dobro to jednak niewypał i zmieniają swoje cele co tworzy kryzys ale nie na taką skalę. Firmy produkujące niepotrzebne ludziom dobra (albo w nieodpowiednich dla ludzi cenach) powinny upadać! I im szybciej tym mniejszy kryzys. W tym przypadku błędną inwestycją była inwestycja banków w ludzi, którzy nie byli w stanie spłacić kredytu. A upadek był tak bolesny, ponieważ rząd go przedłużał płacąc za tych ludzi.

Na sam już koniec czy nie wydaje wam się naturalne, że ceny powinny raczej cały czas maleć aniżeli rosnąć? Przecież technologia cały czas idzie do przodu, wytwarza się więc coraz taniej, a mimo to ceny rosną. Związane jest to z naszym pierwszym przykładem, w którym najpierw rośnie popyt, a dopiero potem podaż. W normalnej sytuacji to najpierw rośnie podaż, który wytwarza popyt (prawo Saya). I znowu wiele osób uważa, że to źle, że ceny cały czas spadają, gdyż to prowadzi do spadku konsumpcji. Dlaczego?

Otóż wydaje im się, że jeśli wiesz, że dzisiaj mieszkanie kosztuje 400 tys. zł i za rok będzie kosztować 350 tys. zł to oczywiste jest, jak twierdzą, że wstrzymasz się z zakupem do przyszłego roku. Ale za rok będzie ta sama sytuacja, gdyż za dwa lata cena spadnie do 300 tys. zł więc w efekcie nigdy mieszkania nie kupisz.

Ciężko o większą bzdurę. Jeśli wszyscy będą się wstrzymywać z kupnem mieszkania to zmaleje popyt czyli cena nie będzie spadać, a rosnąć! Poza tym np. komputery na początku swej egzystencji kosztowały o wiele więcej niż kosztują teraz. Samochody zresztą również. I jakoś ludzie wtedy też je kupowali i kupują je nadal. Człowiek na szczęście nie sugeruje się tylko ceną za jaką chce coś kupić ale przede wszystkim chodzi mu o posiadanie. Co mi po tym, że być może za 60 lat kupie mieszkanie za 1000 zł jak do tego czasu nie będę miał gdzie mieszkać? :)

sobota, 23 marca 2013

Programowanie webowe w Haskellu cz. 3

Trochę ostatnio zaniedbałem bloga za co bardzo przepraszam. Dzisiaj tematem będą własne bardziej wyrafinowane znaczniki w szablonach. Postaramy się wykonać znacznik <if>, który będzie działał według poniższej koncepcji.

Koncepcja

Pomysł polega na tym aby w szablonie móc wpisać coś takiego:
<if>
<equal a="${name}" b="kogut"/>
<then><b>My name is kogut</b></then>
<else>My name is not kogut</else>
</if>
<if>
<not><equal a="${name}" b="kogucik"/></not>
<then><b>My name is not kogucik</b></then>
<else>My name is kogucik</else>
</if>


Wydaje mi się, że koncepcja jest w miarę jasna. Po prostu jeżeli w pierwszym ifie ${name} będzie równe "kogut" to na stronie pojawi się My name is kogut, a w przeciwnym razie "My name is not kogut". Drugi natomiast wyświetli My name is not kogucik jeżeli ${name} będzie różne od "kogucik". Inaczej dostaniemy "My name is kogucik".

Funkcja addSplices

Aby dodać znaczniki, które będą rozpoznawalne we wszystkich szablonach potrzebujemy użyć funkcji addSplices. Podobnie jak funkcja renderWithSplices przyjmuje on listę par ("nazwa znacznika", wartość). Tą wartością tym razem będzie specjalnie przez nas przygotowana funkcja analizująca nieco znaczniki wewnątrz naszego znacznika "if". Zatem dopisujemy w pliku Site.hs w funkcji app, gdzieś przed funkcją return taką oto treść:
addSplices [("if", ifSplice)]

Jako, że za chwilę utworzymy sobie nowy plik o nazwie Splices.hs to od razu możemy go tutaj zaimportować. W tym celu na górze pliku, w miejscu gdzie występują instrukcje import dopisujemy:
import Splices

Nowe znaczniki

Jak wspomniałem już wcześniej utwórzmy sobie plik o nazwie "Splices.hs". Jego treść, którą za chwilę omówię będzie wyglądać następująco:


Najważniejszą funkcją w tym pliku jest ifSplice. Działa ona następująco:
  • najpierw pobieramy wszystkie dzieci elementu "if", przetwarzając dodatkowo elementy "not" i "equal" (gdyby się one tam znalazły) odpowiednimi funkcjami - to realizuje właśnie funkcja runChildrenWith,
  • następnie sprawdzamy czy w ogóle jakieś dzieci występują (po podmianie znaczników "equal" i "not"),
  • jeżeli nie to wyświetlamy komunikat błędu,
  • w przeciwnym razie pobieramy pierwszy element wewnątrz "if" i sprawdzamy czy jest to zwyczajny tekst,
  • jeżeli nie to znów wyświetlamy komunikat błędu, gdyż oczekujemy, że pierwszy element będzie równy "1" lub "0",
  • jak już mamy tekst to sprawdzamy czy jest równy "1",
  • jak tak to wyszukujemy element "then" i wyświetlamy jego dzieci,
  • jeżeli tekst jest różny od "1" to szukamy elementu "else" i wyświetlamy jego zawartość.

Funkcja equalSplice jest bardzo prosta. Pobiera ona znacznik "equal" (za pomocą funkcji getParamNode, która zwraca aktualnie przetwarzany znacznik, a wiemy, że jest nim "equal") i sprawdza czy istnieją argumenty o nazwie "a" i "b". Jeżeli tak to porównuje ze sobą zawartość obu argumentów i jeżeli są równe to zwraca tekst "1", a jeżeli nie to "0".

Funkcja notSplice również jest prostą funkcją. Najpierw pobiera swoje dzieci. Później sprawdza czy jest to dokładnie jeden element i to tekstowy. Jeżeli jest on równy "1" to zwraca "0", w przeciwnym razie zwraca "1". Tak na marginesie, w szablonie znacznik <not> może mieć wiele dzieci. Chodzi o to aby po podmienieniu wszystkich znaczników wewnętrznych pozostał tylko jeden. Podmianę zdefiniowanych znaczników wewnątrz elementu "not" realizuje funkcja runChildren. Dlatego właśnie możemy w szablonie umieścić <not><equal ... /></not>.

W tym momencie możemy już skompilować naszą aplikację, umieścić odpowiednie znaczniki w szablonie (np. te pokazane w koncepcji), uruchomić ją i nacieszyć oczy. Spróbujcie sobie w ramach ćwiczenia dopisać znaczniki "lt" i "gt" zwracające "1" odpowiednio, gdy argument a < argument b oraz dla drugiego znacznika, gdy argument a > argument b. W kolejnej części zajmiemy się obsługą bazy danych. To do następnego razu i powodzenia!