środa, 28 grudnia 2011

Mosty w grafie - Haskell

Zbliża się powrót do szkoły więc trzeba powrócić do implementowania algorytmów. Jako projekt do wykonania dostałem napisanie algorytmu znajdującego mosty w grafie i działającego w czasie O(n+m), gzie n to liczba wierzchołków, a m to liczba krawędzi. Moje upodobania na szczęście się przez święta nie zmieniły więc napisałem to w Haskellu.

Algorytm znajdowania mostów wykorzystuje algorytm DFS. Zmiany są niewielkie, a mianowicie:
  • przeglądając dany wierzchołek nadajemy mu od razu kolejny numer,
  • nadajemy mu także początkową wartość low taką samą jak numer wierzchołka,
  • dalej postępujemy jak w algorytmie DFS, czyli przeglądamy wszystkie sąsiadujące wierzchołki i jeżeli są nieodwiedzone to wrzucamy je na stos po czym wywołujemy procedurę rekurencyjnie z nowym stosem,
  • wracając z rekurencji sprawdzamy wszystkie sąsiadujące wierzchołki, różne od tego, z którego przyszliśmy (czyli różne od ojca) i jeżeli sąsiad był odwiedzony i ma wartość low mniejszą od wierzchołka, w którym jesteśmy to zmieniamy naszą wartość low na wartość tego sąsiada.

Po wykonaniu tej procedury, wierzchołki, które mają numer równy low tworzą mosty.

Algorytm stanie się bardziej jasny jak przeanalizujemy kod:


Linie od 7 do 10 definiują nam typy danych, tak aby się nam lepiej programowało. Wierzchołek (Vertex) ma jak widać jakiś numer, jakąś listę krawędzi oraz jakąś wartość low. Krawędź to po prostu liczba, a most z kolei to para liczb oznaczająca parę wierzchołków tworzących jeden most.

Funkcja findBridges przyjmuje tablicę wierzchołków i zwraca listę mostów. Dalej treść funkcji czytamy od końca. Zatem wywołujemy funkcję dfsBridges dla pierwszego wierzchołka i naszej tablicy, która uzupełni nam podanym wyżej algorytmem wartości low oraz numery wierzchołków i zwróci te dane jako nową tablicę wierzchołków. Funkcja assocs zamieni nam tablicę na listę par typu (indeks, Vertex). Przeglądamy tą listę (foldr) i dla każdej pary sprawdzamy czy wartość low jest równa numerowi wierzchołka (czyli wyszukujemy wierzchołki tworzące mosty). Jeżeli nie to pomijamy ten wierzchołek. Natomiast jeżeli tak to bierzemy z listy sąsiadów tylko te wierzchołki, które mają wartość low różną od wartości low danego wierzchołka (filter). Dalej z tej listy tworzymy pary (mosty) w ten sposób: bierzemy numer naszego wierzchołka jako pierwszą liczbę w parze oraz dany element uzyskanej przed chwilą listy jako drugą liczbę (zip). Na koniec zostaje nam dołączenie tej listy do listy już uzyskanej.

Funkcja dfsBridges przyjmuje "stos" (w naszym przypadku po prostu listę) indeksów wierzchołków, ich tablicę oraz aktualny numer do przydzielenia. Zwraca natomiast uzupełnioną tablicę wierzchołków o numer i wartość low. Tą funkcję zaczynamy czytać od where. Na początku inicjalizujemy wartości naszego wierzchołka czyli przydzielamy mu numer oraz low równy aktualnemu numerowi - wartość d. Odbywa się to w ten sposób, że tworzymy nową tablicę wierzchołków (initArr), która zawiera te same dane za wyjątkiem naszego wierzchołka (//). Dalej obliczamy nowy "stos" przeglądając sąsiadów aktualnego wierzchołka i jeżeli któryś z nich nie był jeszcze odwiedzony (czyli ma numer większy niż -1) to wrzucamy go na koniec listy.

Kolejnym krokiem jest wywołanie naszej funkcji rekurencyjnie z nowym "stosem" i nową tablicą oraz kolejnym numerem. Jak przypadkiem skończy się nam "stos" (czyli lista będzie pusta, czyli wszystkie wierzchołki zostały odwiedzone) to zwracamy uzyskaną tablicę wierzchołków. Wracając z rekurencji przeglądamy sąsiadów naszego wierzchołka ale z nowo uzyskanej tablicy. Dalej jest już jak w algorytmie czyli jak któryś z sąsiadów był odwiedzony i nie jest to wierzchołek, z którego przyszliśmy oraz ma mniejszą wartość low to bierzemy jego wartość low. Na koniec zmieniamy tablicę tak aby nasz wierzchołek uzyskał tą nową wartość low i ją zwracamy.

Jeżeli ktoś jeszcze nie za bardzo rozumie co to jest ta wartość low to w skrócie można powiedzieć, że jeżeli napotkamy cykl w grafie to wszystkim wierzchołkom w tym cyklu nadajemy jednakową wartość low (obrazowo można powiedzieć, że kolorujemy cykle w grafie).

To właściwie wszystko. Czekam na komentarze.

poniedziałek, 26 grudnia 2011

Parsowanie plików pcap w Haskellu

Dzisiaj przykład prostego parsowania w Haskellu. Zadanie to znalazłem na stronie firmy Tsuru Capital jako code sample do napisania, gdybyśmy ubiegali się o stanowisko programisty. Opis zadania znajduje się tutaj. Jest tam dostępny także przykładowy plik z danymi w formacie pcap.

W skrócie to co mamy zrobić to:
  • wyciągnąć z pliku linie, które zaczynają się od znaków B6034,
  • daną linię sparsować,
  • i wypisać ją w ten sposób:
       @ ... @ @ ... @
  • To jak dokładnie wygląda linia danych jest opisane na stronie z opisem zadania. Generalnie wystarczy wypisać tylko część danych z linii oraz w nieco innej kolejności.
  • uruchamiając nasz program z parametrem "-r" powinien on sortować dane po zawartej w linii informacji "accept-time". Dla ułatwienia podana jest informacja, że różnica między czasem wysłania pakietu (czyli pkt-time) a accept-time nigdy nie jest większa niż 3 sekundy.

No to zaczynamy. Na początku warto wspomnieć o pakiecie Network.Pcap, który ułatwi nam czytanie z takich plików. Jedyne co nam pozostaje zrobić to parsowanie linii, ewentualne jej sortowanie oraz wypisanie przeczytanych danych. Zacznijmy od zdefiniowania sobie głównych typów danych:

module Main where

import Network.Pcap
import qualified Data.ByteString.Char8 as BS

data MarketData = MarketData {
      issueCode  :: BS.ByteString
    , bids       :: [(BS.ByteString, BS.ByteString)]
    , asks       :: [(BS.ByteString, BS.ByteString)]
    , acceptTime :: AcceptTime
    }

data AcceptTime = AcceptTime {
      hh :: BS.ByteString
    , mm :: BS.ByteString
    , ss :: BS.ByteString
    , uu :: BS.ByteString
    }

Jako, że pakiet Network.Pcap dostarcza nam danych w ByteStringach to wszędzie będziemy ich używać. Na uwagę zasługują pola bids i asks w MarketData. Otóż są to pary, w których pierwsza wartość to cena a druga to liczba danego towaru. Stworzone wyżej typy danych reprezentują jedną linię w pliku z danymi. Teraz sprawmy aby się automatycznie dobrze wypisywały:

instance Show AcceptTime where
    show (AcceptTime h m s u) = (BS.unpack h) ++ ":"
                                              ++ (BS.unpack m)
                                              ++ ":"
                                              ++ (BS.unpack s)
                                              ++ "."
                                              ++ (BS.unpack u)

instance Show MarketData where
    show (MarketData i b a t) = (show t)
        ++ " " ++ (BS.unpack i)
        ++ " " ++ (drop 1 $ foldr (\(ty, pr) ac ->
            "@" ++ (BS.unpack ty) ++ "@" ++ (BS.unpack pr) ++ ac) [] b)
        ++ " " ++ (drop 1 $ foldr (\(ty, pr) ac ->
            "@" ++ (BS.unpack ty) ++ "@" ++ (BS.unpack pr) ++ ac) [] a)

W ten sposób wystarczy wywołać funkcję show na typie MarketData i otrzymamy linię opisaną w zadaniu.

Przechodzimy zatem do najtrudniejszej części czyli parsowania danych. W pakiecie Network.Pcap dostępna jest funkcja dispatch, która przyjmuje otworzony plik (może to być również gniazdo sieciowe), liczbę pakietów do przeczytania (w naszym przypadku -1 czyli cały plik) oraz funkcję, która będzie przetwarzać dany pakiet o typie PktHdr -> ByteString -> IO (). Zanim jednak przejdziemy do napisania funkcji parsującej przypomnijmy sobie warunek czytania danego pakietu. Otóż musi się on zaczynać od znaków B6034, a to z kolei oznacza, że mogą pojawić się pakiety, które tego warunku spełniać nie będą. W takim wypadku musimy je pominąć. Napiszmy więc funkcję sprawdzającą ten warunek i jeżeli on zachodzi to parsujemy daną linię i ją wyświetlamy, a jeżeli nie to nic nie robimy. Nazwijmy ją showData:

prefix :: BS.ByteString
prefix = (BS.pack "B6034")

showData :: PktHdr
         -> BS.ByteString
         -> IO ()
showData hdr dat = do
    let (_, _d) = BS.breakSubstring prefix dat
        (sec, msec) = (hdrTime hdr) `divMod` 1000000
    if (BS.length _d) > 0
        then putStrLn $ (show $ TOD (toInteger sec) (toInteger (msec*1000000)))
            ++ " " ++ (show $ parseData _d)
        else return ()

To wszystko co występuje przed wywołaniem funkcji parseData to wyświetlenie po prostu pkt-time, który nie jest zawarty w danych ale w nagłówku pakietu. Sprawdzanie odbywa się w ten sposób, że dzielimy linię na dwie części: tam gdzie występuje prefix i to co przed nim. Jeżeli uda się podzielić linię w ten sposób to znaczy, że możemy parsować:

parseData :: BS.ByteString
          -> MarketData
parseData line = MarketData (BS.take 12 l) _bids _asks (AcceptTime h m s u)
  where
    l = BS.drop 5 line
    (_bids, r1) = takeBids $ BS.drop 24 l
    (_asks, r2) = takeAsks $ BS.drop 7 r1
    (h, r3) = BS.splitAt 2 (BS.take 8 $ BS.drop 50 r2)
    (m, r4) = BS.splitAt 2 r3
    (s, u) = BS.splitAt 2 r4
 
takeBids :: BS.ByteString
         -> ([(BS.ByteString, BS.ByteString)], BS.ByteString)
takeBids bs = takeBids' bs (5::Integer)
  where
    takeBids' b n
        | n > 0     = let (_b, _r) = takeBids' (BS.drop 12 b) (n-1)
                      in (_b ++ [(BS.splitAt 5 $ BS.take 12 b)], _r)
        | otherwise = ([], b)
 
takeAsks :: BS.ByteString
         -> ([(BS.ByteString, BS.ByteString)], BS.ByteString)
takeAsks bs = takeAsks' bs (5::Integer)
  where
    takeAsks' b n
        | n > 0     = let (_b, _r) = takeAsks' (BS.drop 12 b) (n-1)
                      in ((BS.splitAt 5 $ BS.take 12 b):_b, _r)
        | otherwise = ([], b)

Funkcja parseData jest bardzo prosta. Pobiera ona kolejną konkretną ilość znaków z linii i wrzuca je odpowiednio do konstruktora MarketData. Np. jako pierwszy parametr konstruktor przyjmuje issueCode, który według opisu zadania ma długość 12 znaków. W tym celu najpierw pomijamy pierwsze 5 znaków warunkowych (czyli B6034) po czym bierzemy kolejne 12 właśnie jako issueCode itd. Na uwagę zasługują jeszcze dwie funkcje takeBids oraz takeAsks. Są one niemal identyczne - różnią się kolejnością dodawania czytanych elementów do listy. Pierwsza odwraca kolejność a druga pozostawia ją bez zmian. Obie natomiast czytają dane rekurencyjnie biorąc 12 znaków i dzieląc je na dwie listy od 1 do 5 znaku oraz od 6 do 12. Powtarzają tą czynność tak długo aż uzyskają 5 par danych (parametr n).

Pozostaje nam tylko dopisanie pozostałych funkcji odpalających cały mechanizm:

readQuote :: Bool
          -> PcapHandle
          -> IO ()
readQuote False h = dispatchBS h (-1) showData >> return ()
readQuote True h = return ()
 
main :: IO ()
main = do
    opt <- liftA parseOptions getArgs
    hdl <- openOffline $ filename opt
    readQuote (quoteOrder opt) hdl
 
data Options = Options { filename :: FilePath, quoteOrder :: Bool }

parseOptions :: [String]
             -> Options
parseOptions [] = Options "" False
parseOptions (x:xs)
    | x == "-r" = Options (head xs) True
    | otherwise = Options x False

Dodałem tu nowy typ danych Options, który odpowiada za parametry programu, takie jak nazwa pliku oraz czy ma być sortowany. Funkcja openOffline zwraca nam uchwyt do otworzonego pliku pcap, a funkcja readQuote przyjmuje jako parametr czy ma sortować dane (jeżeli tak to nic nie robi) i dalej wywołuje funkcję dispatchBS, która przetwarza dane wywołując showData na każdym pakiecie.

To by było na tyle. Sortowanie oczywiście pozostawiam czytelnikom do samodzielnej realizacji :)

Jakby jednak ktoś chciał zobaczyć jak ja wykonałem sortowanie danych to tutaj jest cały plik.

piątek, 16 grudnia 2011

Vim Hoogle

Dzisiaj coś dla programistów Haskella. Ostatnio znalazłem bardzo ciekawy plugin do Vima integrujący ten wspaniały edytor z równie wspaniałą wyszukiwarką Hoogle. Jeżeli ktoś jeszcze o niej nie słyszał to w skrócie jest to wyszukiwarka, która umożliwia nam znalezienie funkcji haskellowych w dostępnych bibliotekach (z hackage.haskell.org) po nazwie oraz po typie. Dodatkowo po kliknięciu na znalezionej funkcji odsyła nas do jej dokumentacji.

Plugin zwie się vim-hoogle i jest dostępny tutaj. Instaluje się go standardowo - ściągamy sobie archiwum z github i wypakujemy zawartość do katalogu ~/.vim/
Aby plugin działał poprawnie wymagane jest jeszcze zainstalowanie programu hoogle. Najłatwiej jest to zrobić wykorzystując program cabal:

$ cabal install hoogle

Po czym należy jeszcze ściągnąć bazę danych w ten sposób:

$ hoogle data

Na koniec możemy uruchomić Vima i np. wywołać :Hoogle foldr. Po więcej informacji odsyłam do :help hoogle.

sobota, 10 grudnia 2011

Cieniowanie oraz poziome klamry w MetaPost-cie

Ostatnio pisząc pewien referat natknąłem się na problem w LaTeXu. Otóż po pierwsze chciałem narysować sobie prostokąt, którego część będzie wykreskowana (wycieniowana) ukośnymi liniami. Po drugie potrzebowałem narysować pod figurą taką poziomą klamrę z podpisem. Generalnie chciałem uzyskać coś takiego:


Na pierwszy ogień poszedł MetaPost i jak się okazało świetnie się do tego nadaje. Wystarczyła w sumie prosta pętelka (właściwie to dwie) for i wszystko ładnie wyszło. Oto kod odpowiedzialny za cieniowanie:

for i=0 upto width/2: 
  draw (x,y-i*2)--(x+i*2,y);
endfor

for i=1 upto width/2: 
  draw (x+i*2,y2)--(x2,y - i*2);
endfor

Koncepcja jest bardzo prosta. Jeżeli mamy kwadrat do wykreskowania to dzielimy go na dwie części wzdłuż przekątnej, która będzie również kreską. Dzielimy go z tego powodu, że długości kresek rosną do połowy kwadratu a później maleją. Stąd potrzebne są również dwie pętle.

Druga rzecz to pozioma klamra. Długo się naszukałem jak to zrobić więc jak już w końcu znalazłem to się tą wiedzą podzielę. Cały trick polega na wyrenderowaniu zwykłej klamry z czcionki, rozciągnięcie jej i obrócenie. Kod:

label.bot(btex $\lbrace$ etex xscaled 1.5 yscaled 15 rotated 90, (x,y));

Na koniec zamieszczam cały kod jak wygenerować obrazek przedstawiony powyżej:
prologues:=3;
verbatimtex
%&latex
\documentclass{minimal}
\begin{document}
etex
beginfig(0);
s:=20;

draw (350,s+25)--(350,s+125)--(500,s+125)--(500,s+25)--cycle;
draw (350,s+125)--(400,s+75)--(400,s+25);
draw (400,s+75)--(450,s+25);
draw (350,s+75)--(500,s+75) dashed evenly scaled 2;
label.lft(btex $m/2$ etex, (350,s+100));
label.lft(btex $m/2$ etex, (350,s+50));
label.top(btex $p$ etex, (425,s+125));
label(btex $H$ etex, (450,s+100));
label(btex $U_2$ etex, (475,s+50));

for i=0 upto 25: 
  draw (350+i,s+125-i)--(350,s+125-i*2);
endfor

for i=1 upto 25: 
  draw (375+i,s+100-i)--(350+i*2,s+75);
endfor

for i=0 upto 25: 
  draw (350,s+75-i*2)--(350+i*2,s+75);
endfor

for i=1 upto 25: 
  draw (350+i*2,s+25)--(400,s+75 - i*2);
endfor

for i=0 upto 25: 
  draw (400+i,s+75-i)--(400,s+75-i*2);
endfor

for i=1 upto 25: 
  draw (425+i,s+50-i)--(400+i*2,s+25);
endfor

s:= 40;

label.bot(btex $\lbrace$ etex xscaled 1.5 yscaled 15 rotated 90, (425,s));

labeloffset:=5.5mm;
label.bot(btex $U$ etex, (425,s));


endfig;
end

poniedziałek, 28 listopada 2011

O wyższości języków

Zazwyczaj unikam rozmów na temat wyższości jednego języka (programowania) nad drugim. Głównie ze względu na to, że każdy programista ma jakiś swój ulubiony język i przy takiej dyskusji stosuje bardziej emocjonalne argumenty niż rzeczowe. Niestety ostatnio zostałem w taką dyskusję wciągnięty.

Człowiek, z którym rozmawiałem jest ewidentnym zwolennikiem programowania w C# pod platformą Windows. I tak jak się obawiałem pytania, które padały były emocjonalne (tzn. nie za bardzo dotyczyły tematu rozmowy). Na przykład ów człowiek zapytał mnie - niby całkiem rzeczowo - dlaczego warto programować w Haskellu. Dodam jeszcze, że on o tym języku w ogóle nie słyszał. Pytanie byłoby jak najbardziej na miejscu, gdyby nie dalsza część, którą dopowiedział chwilę później, a mianowicie jak została rozwiązana komunikacja z maszyną wirtualną od C# i czy komunikacja z Windowsem odbywa się za pomocą obiektów COM+.

Widać zatem, że pytanie w całości nie dotyczyło w ogóle cech języka programowania tylko raczej sposobu w jakim zostały napisane biblioteki. Ewidentnie oczekiwał on odpowiedzi, która wyraziłaby wyższość C# nad Haskellem, ponieważ komunikacja z Windowsem jest bardziej efektywna w C#. To przypomina mi bardzo pytanie o to co da się w danym języku napisać. Odpowiedź brzmi wszystko, w tym również da się napisać efektywną komunikację z Windowsem ale to nigdy nie powinno dotyczyć porównywania języków programowania (co najwyżej środowisk programistycznych lub wsparcia). Na marginesie dodam, że jeśli nawet w Haskellu nie da się napisać szybko działającej jakiejś krytycznej części programu to zawsze można to napisać chociażby w C, z którym Haskell świetnie się integruję (za pomocą modułu FFI).

Moim skromnym zdaniem takie rozmowy powinny dotyczyć zalet elementów składniowych języka a nie możliwości jego bibliotek. Wiem, że większość programistów uważa, że dzisiaj język bez dobrego zaplecza w postaci ogromu bibliotek i najlepiej jakiejś firmy (korporacji), która za tym stoi jest nic nie wart. Ja jednak na takie zarzuty odpowiadam, że w przypadku gdy w jednym języku nie ma napisanej danej funkcjonalności a w drugim jest to faktycznie ten drugi wypada lepiej. Jednak jeśli danej funkcjonalności nie ma ani tu ani tu to wygrywa język, w którym piszę się szybciej, krócej i poprawniej.

Na dłuższą metę koszt poniesiony na początku w postaci napisania funkcjonalności bardzo szybko się zwróci w przypadku takich właśnie języków. To tak jak z algorytmami działającymi lepiej gdy dane są posortowane - sortowanie danych faktycznie zajmuje trochę czasu ale gdy dane są odpowiednio duże (czyli w naszym przypadku program) to nie ma on żadnego znaczenia a czas działania algorytmu znacząco się skraca.

poniedziałek, 21 listopada 2011

UML a Haskell

Dzisiaj chciałbym poruszyć temat projektowania aplikacji haskellowych przy użyciu UMLa. Zajmę się tylko diagramem klas, gdyż akurat ostatnio próbowałem się z nim zmierzyć (w kontekście aplikacji haskellowych). Aby jednak zacząć w ogóle mówić o diagramie klas musiałbym pokrótce omówić typy danych jakie występują w Haskellu oraz ich podobieństwa do typów używanych w językach imperatywnych orientowanych obiektowo jak np. C++ czy Java.

W Haskellu rozróżniamy głównie 2 słowa kluczowe służące do tworzenia typów: class oraz data (istnieje również type, który tworzy tylko alias do typu oraz newtype, który jest raczej zabiegiem optymalizacyjnym). Oczywiście w językach funkcyjnych również funkcja jest typem danych. Omówię teraz z grubsza wyżej wymienione słowa kluczowe. Zapisy tłumaczył będę imperatywnie, tzn. używając słów takich jak chociażby interfejs, które oczywiście nie występują w Haskellu.

data

Zacznę o słówka data, ponieważ jest ono mniej abstrakcyjne od class. Typy tworzone przez właśnie to słowo odpowiadają klasą znanym z języków obiektowych. Wygląda to mniej więcej tak:

data Foo = Foo { fooname :: String, foofunction :: Int -> String }

Tłumacząc to na język imperatywny powiedzielibyśmy, że Foo jest klasą, która zawiera atrybut o nazwie fooname typu String oraz metodę o nazwie foofunction, która przyjmuje obiekt typu Int jako argument i zwraca obiekt typu String. Jak zatem tworzymy instancję klasy? Ano tak:

fooInstance :: Foo
fooInstance = Foo "foo" (\a -> "foo: " ++ (show a))

Pierwsza różnica jaką widać w stosunku do języków imperatywnych to fakt, że możemy przyjąć funkcję jako argument konstruktora. Oczywiście w językach typu C-podobnych moglibyśmy wykorzystać w tym celu wskaźnik do funkcji bądź delegaty. Rozważmy zatem nieco bardziej wyrafinowany typ:

data Foo2 = Foo2 { fooname :: String, foofunction :: a -> String }

Zmieniła się definicja foofunction. Moglibyśmy przeczytać ją następująco: foofunction jest funkcją, która przyjmuje argument dowolnego typu i zwraca obiekt typu String. Aby wyrazić taką funkcję w imperatywnych językach potrzebny jest jakiś mechanizm template'ów. OK, a co powiedzie na taki typ:

data Foo3 = Foo3 { fooname :: String, foofunction :: (Show a) => a -> String }

Teraz foofunction jest funkcją, która przyjmuje argument dowolnego typu, który implementuje interfejs Show i zwraca obiekt typu String. W Javie po prostu funkcja ta przyjmowałaby jako argument ten interfejs a raczej "obiekt interfejsu". No dobrze, a co w takim przypadku:

data Foo3 = Foo3 { fooname :: String, foofunction :: (Show a, Ord a) => a -> String }

W tym przypadku foofunction jest funkcją, która przyjmuje argument dowolnego typu, który implementuje interfejs Show oraz interfejs Ord i zwraca obiekt typu String. Teraz jest znacznie ciężej wyrazić to w Javie. Trzeba by utworzyć osobny typ (klasę), który implementuje te interfejsy i użyć go jako argument funkcji.

Typy tworzone przez data mogą być również parametryzowane:

data Foo3 b = FooB { fooid :: (Show b) => b, foofunction :: (Show a, Ord a) => a -> String }

Obiekt tworzylibyśmy w ten sposób:
fooInstance :: Foo3 Int
fooInstance = FooB 3 (\a -> "foo: " ++ (show a))

Natomiast obiekt takiej klasy przyjmowalibyśmy w funkcji np. w taki sposób:
fooFun :: Foo3 b -> b
fooFun foo = fooid foo

fooFunSpec :: Foo3 Int -> Int
fooFunSpec = fooid foo

Pierwsza funkcja jest polimorficzna czyli zadziała dla każdego typu Foo3, a druga tylko dla typu Foo3 Int.

class

Klasy w Haskellu nie są tym samym co klasy w Javie czy w C++. Przypominają one raczej interfejsy znane z Javy tylko znacznie lepsze. Prosty przykład:

class Show a where
    show :: a -> String

Jak widać jest to prosty interfejs z metodą show, która przyjmuje argument typu a i zwraca obiekt typu String. Jest to tylko deklaracja typu funkcji bez jej ciała. Jak widać również tutaj w przypadku języków imperatywnych trzeba by zastosować mechanizm template'ów. Jak zatem implementuje się ten interfejs? A tak:
instance Show Foo where
    show _ = "Foo"

W tym momencie na dowolnym obiekcie klasy Foo możemy wywołać funkcję show i zawsze w wyniku otrzymamy napis Foo. Pominę może aspekt, że metody/atrybuty w interfejsach mogą przyjmować domyślne definicje co różni je od interfejsów w Javie.

Podstawy za nami więc pora na bardziej skomplikowany typ:
class (Ord a) => Show a where
    show :: a -> String

Ten zapis powoduje, że implementować interfejs Show mogą klasy, które implementują już interfejs Ord. Czy da się coś takiego wyrazić w językach imperatywnych? Ja nie znam żadnej konstrukcji ale też żadnym mistrzem w C++ czy Javie nie jestem więc jak ktoś wie jak można to zrobić to proszę niech napisze w komentarzu.

To nie jedyne różnice. Pokażmy zatem coś znacznie ciekawszego:
class Eq e => Collection c e | c -> e where
    ...

Zapis po kresce oznacza, że typ e jest jednoznacznie określony przez typ c. Co to oznacza? Oznacza to tyle, że jak istniała by instancja powiedzmy Collection Array Int to nie może istnieć inna klasa implementująca ten interfejs taka, że c będzie typu Array, a e NIE będzie typu Int.

Nie dodałem tu jeszcze, że interfejsy mogą implementować typy polimorficzne takie jak chociażby lista elementów dowolnego typu: [a]. Czyli taki zapis:
instance Collection [a] a where
    ...

w tym przypadku oznacza, że "[a] a" jest kolekcją i żadna inna kolekcja "zaczynająca się" od [a] istnieć nie może. Oznacza on również, że "[Int] Int" jest kolekcją oraz że "[Char] Char" jest kolekcją itd. Tak na marginesie to widać tu jeszcze jedną rzecz, której nie da się (chyba) wyrazić w wymienionych językach imperatywnych, tzn. "[a] a" - jak pierwszy typ będzie powiedzmy [Int] to drugi musi być Int (ponieważ a jest typu Int).

UML

Powróćmy zatem do UMLa. O ile typy tworzone za pomocą słówka data i relacje między nimi da się jakoś na siłę wyrazić na diagramie klas o tyle już w przypadku słówka class nie za bardzo. No bo jak ma z diagramu wynikać, że np. klasa (słówko data) "Article a" ma być taka, że a implementuje interfejs (słówko class) Foo, a do tego interfejs Foo mogą implementować tylko te klasy, które implementują jeszcze interfejs Show oraz interfejs Ord? Nie mówiąc już o funkcjach istniejących poza interfejsami/klasami.

Czego zatem użyć zamiast diagramu klas skoro się nie nadaje? W UMLu nie ma moim zdaniem odpowiedniego diagramu. Pewien człowiek napisał propozycję diagramów do projektowania funkcyjnego w swoim doktoracie: link. Większość ludzi jednak uważa, że w przypadku nie zbyt dużych programów wystarczy pisać program w odpowiedni sposób, który Haskell akurat wymusza. Jaki to konkretnie sposób? Odsyłam do książki "Real World Haskell". W przypadku większych aplikacji polecam użyć jakiegoś prostego programu do tworzenia diagramów chociażby jak CmapTools. Tyle, że sami musimy ustalić sobie jak ma być prezentowana klasa, jak interfejs a jak funkcja itd. Niestety kodu nam to nie wygeneruje ale aplikację uda się z powodzeniem zaprojektować.

wtorek, 15 listopada 2011

Multikolorowanie grafu w Haskellu

Ostatnio na studiach dostałem takie zadanie do zaimplementowania:

Część I:
Wygenerować plik w formacie GXL zawierający graf składający się ze stacji, które są losowe rozmieszczone wewnątrz miasta, w którym stacje są połączone krawędzią wtedy, gdy ich zasięg się pokrywa.

Wejście:
  • Współrzędne środka koła (miasta)
  • Promień koła (miasta)
  • Liczba stacji
  • Zasięg (promień) stacji

Wyjście:
Uzyskany graf w formacie GXL.

Część II
Należy wczytać wygenerowany wcześniej graf oraz zapotrzebowanie na częstotliwości dla każdej stacji. Stacje, których zasięg się pokrywa nie mogą uzyskać tych samych częstotliwości. Program powinien znaleźć jak najmniejszą liczbę różnych częstotliwości potrzebnych aby stacje mogły funkcjonować.

Przykład:
Mając prosty graf [1]->[2]->[3] i wiedząc, że stacja 1 potrzebuje 3 częstotliwości, stacja 2 potrzebuje 4 a stacja ostatnia 5. Wówczas powinniśmy przydzielić częstotliwości np. tak:
  • Stacja nr 1: 4,5,6
  • Stacja nr 2: 0,1,2,3
  • Stacja nr 3: 4,5,6,7,8
i wynikiem działania programu powinna być liczba 9. Jak widać stacja pierwsza i ostatnia może korzystać z tych samych częstotliwości, ponieważ ich zasięg się nie pokrywa. Stacja druga natomiast musi korzystać z innych częstotliwości niż stacja 1 i 3.

Wejście:
  • Plik GXL
  • Zapotrzebowanie na częstotliwości dla każdej stacji

Wyjście:
  • Liczba przydzielonych różnych częstotliwości

Problem postanowiłem rozwiązać w Haskellu z uwagi na możliwość operacji na listach nieskończonych co w tym przypadku bardzo ułatwia znajdowanie częstotliwości. W części II wykorzystałem algorytm SLF. Oczywiście problem jest NP zupełny więc nie ma idealnego algorytmu, który by przydzielił zawsze najmniejszą możliwą do uzyskania liczbę częstotliwości dla każdego przypadku.

Część I: gxl.hs
Część II: multicolor.hs
Plik do obsługi formatu GXL (wymagany aby skompilować poprzednie 2 pliki): gxl_dtd.hs

niedziela, 13 listopada 2011

Leniwa potęga

Dla odmiany dzisiaj napiszę o Haskellu. Poznałem ten język stosunkowo niedawno (jakieś 1,5 roku temu) więc nie jestem żadnym ekspertem w temacie. Jednakże jedna rzecz nadzwyczaj mi się w nim spodobała i miałem nawet okazję ją ostatnio wykorzystać. Jest to jedna z wyróżniających cech tego języka czyli leniwa ewaluacja. Brzmi dość tajemniczo ale koncepcja jest bardzo prosta. Rozważmy prostą funkcję w C:
int check(int a, int b) {
    if (a > 100)
        return 100;
    if (a > b) 
        return b;

    return a;
}

Leniwa ewaluacja spowoduje, że jeżeli akurat zmienna a będzie większa niż 100 to zmienna b nie zostanie w ogóle wyliczona. Zauważmy, że pod zmienną b może kryć się szereg wywołań funkcji ciężkich obliczeniowo. Wówczas nasz program będzie działał znacznie szybciej w Haskellu niż w C (napisany w ten sposób).

Niestety jak ktoś kiedyś napisał, leniwa ewaluacja to nie święty graal programowania, to coś z czym trzeba nauczyć się żyć. Powoduje ona masę problemów związanych z przepełnieniem stosu. Chodzi o to, że żadna zmienna nie jest wyliczana od razu lecz dopiero w momencie gdy zostaje użyta. Zamiast jej wartości odkładany na stos jest adres do instrukcji, które pozwalają ją wyliczyć. Jeśli więc np. funkcja działa rekurencyjnie to może takich adresów sporo odłożyć i dochodzi wówczas do przepełnienia stosu.

Leniwa ewaluacja pozwala jeszcze na jedną rzecz. Mianowicie na definiowanie w programie typów nieskończonych oraz operacji na nich. W Haskellu zatem możliwe jest zapisanie listy wszystkich liczb naturalnych:
[0..] 
Odbywa się to w ten sam sposób jak powyżej. Dopóki nie żaden element z listy nie jest potrzebny to pamiętany jest tylko adres jak wyliczyć kolejny element listy. Oczywiście próba wypisania wszystkich elementów tej listy skończy się w końcu brakiem pamięci. Podam teraz przykład kiedy się to może przydać. Załóżmy, że mamy jakąś (skończoną) listę liczb i chcielibyśmy poznać najmniejszą liczbę naturalną, która nie należy do tej listy (ten problem występuje np. w zagadnieniach kolorowania grafu). W imperatywnym języku musielibyśmy pisać pętle z szeregiem instrukcji warunkowych, które strasznie zaciemniają istotę tego co robimy. Jak zatem wygląda to w Haskellu? A tak:
head $ [0..] // lista 
Te dwa ukośniki (//) to nie żaden komentarz tylko operator usuwający elementy z pierwszej listy (po lewej), które występują w drugiej (po prawej). Przeczytajmy więc to wyrażenie od lewej strony:

Weź pierwszy element (head) z listy powstałej z wyrażenia ($) różnicy (//) liczb naturalnych ([0..]) i listy (lista).

Wygląda to bardzo zwięźle i czytelnie a do tego nie musimy się martwić o warunki do sprawdzenia ani o zakres liczb do przeszukania. Podsumowując warto się zainteresować językami z leniwą ewaluacją lecz trzeba jej używać z głową :)

wtorek, 8 listopada 2011

Zagrajmy w golfa!

Dość ważnym etapem w mojej nauce Vima był serwis vimgolf.com. Niebywały pomysł aby zmobilizować ludzi do głębszego poznania tego wspaniałego edytora. Wydaje Ci się, że znasz Vima? Wejdź na stronę i sprawdź!

Serwis zawiera kilkadziesiąt zadań do wykonania. Zadania polegają na przekształceniu pliku wejściowego tak aby przy użyciu najmniejszej liczby naciśnięć klawiszy uzyskać plik wyjściowy. Jednakże by w ogóle móc się z kimś porównać należałoby mieć tą samą wersję Vima, te same pluginy i te same ustawienia. Nie musisz się o to martwić, autor strony o wszystkim pomyślał.

Aby w ogóle móc brać udział w zabawie należy zarejestrować się na stronie przy użyciu Twittera. Jeżeli więc nie masz konta to musisz je sobie założyć. Gdy się zarejestrujemy uzyskamy klucz (VimGolf key). Później należy zainstalować sobie skrypt napisany przez autora w Rubym za pomocą polecenia:
$ gem install vimgolf
Jeżeli wszystko przejdzie pomyślnie to w dalszym ciągu uruchamiamy polecenie:
$ vimgolf setup
Program zapyta nas o klucz, który uzyskaliśmy rejestrując się na stronie.
Na koniec pozostaje nam wybrać sobie zadanie i wykonać polecenie:
$ vimgolf put [challenge ID]
gdzie [challenge ID] jest podane na stronie z zadaniem. Po wykonaniu powyższego polecenia uruchomi się nam Vim z załadowanym plikiem wejściowym i spełniający wszystkie kryteria, które opisałem w poprzednim akapicie. Dokonujemy w nim niezbędnych zmian i wychodzimy z Vima uprzednio zapisując plik. Skrypt zliczy nasze wciśnięcia klawiszy i zapyta się czy wysłać wynik na serwer.

To wszystko. Zatem do dzieła!

piątek, 4 listopada 2011

Makra w Vimie

Tym razem chciałbym rozwinąć wątek słynnej już zasady wśród vimowców czyli "jak musisz coś zrobić więcej niż dwa razy powinieneś się zastanowić jak lepiej można to zrobić". Zakładam, że osoba czytająca tego posta przeszła chociaż przez vimtutor, bez tego raczej nie warto czytać dalej. Jeżeli jednak masz to już za sobą to przejdę od razu do rzeczy. Chyba nie wiele osób zdaje sobie tak naprawdę sprawę z potęgi drzemiącej w makrach. W tym poście postaram się omówić ich podstawy. Makra to po prostu nagrana sekwencja wykonywanych przez nas czynności. W przypadku Vima są to po prostu wciskane kolejno klawisze. Poniżej omówię na konkretnym przykładzie jak utworzyć makro, przypisać do niego skrót klawiszowy oraz sprawić by nie zniknął po ponownym uruchomieniu Vima.

Tworzenie makr
Nagrywanie makra rozpoczynamy wciskając q oraz literę (a-z). Oznacza ona nazwę rejestru, do którego będą zapisywane wciskane klawisze. Ta informacja przyda nam się później do przypisywania skrótu klawiszowego. Gdy już rozpoczniemy nagrywanie to wykonujemy kolejno czynności, które chcielibyśmy powtarzać, a później kończymy nagrywanie wciskając klawisz q. W tym momencie możemy wykonać makro wciskając @ oraz literę, którą wybraliśmy przy nagrywaniu makra.

Podam teraz przykład bardzo prostego makra. Będzie ono wstawiać zaznaczony tekst w cudzysłowy, czyli jak zaznaczymy tekst Ala ma kota. i wykonamy makro to w efekcie dostaniemy "Ala ma kota.".
Cały trik polega na tym, że wycinamy zaznaczony tekst, wstawiamy cudzysłów, wklejamy tekst i wstawiamy cudzysłów. Wykonujemy więc kolejno:
1. zaznaczamy kawałek tekstu
2. rozpoczynamy nagrywanie makra "a": qa
3. wycinamy tekst: c
4. wstawiamy cudzysłów: "
5. wychodzimy z trybu wstawiania:
6. wklejamy wycięty tekst: p
7. przechodzimy do trybu wstawiania: a
8. wstawiamy cudzysłów: "
9. wychodzimy z trybu wstawiania:
10. kończymy nagrywanie makra: q

Teraz możemy przetestować działanie naszego makra poprzez zaznaczenie dowolnego tekstu i wciśnięcie @a.

Przypisanie skrótu klawiszowego
Skróty tworzy się poleceniem map. Jego składnia z grubsza wygląda tak:
:map <klawisz> <sekwencja>
Aby więc zmapować jakieś makro potrzebujemy nagranej sekwencji. Jak już wspominałem wcześniej makra zostają zapisane do wybranego rejestru, tzn. jeżeli na początku wcisnęliśmy qa to nasza sekwencja znajduje się w rejestrze a. Możemy więc w bardzo prosty sposób ją wyciągnąć. Mianowicie jak już wpiszemy np. :map <C-t> to wciskamy <C-r> po czym naciskamy literę odpowiedniego rejestru czyli w naszym przypadku jest to a. Sekwencja w ten sposób zostanie wklejona po czym pozostaje nam tylko zatwierdzić wciskając enter.

Zapisanie skrótu na stałe
Każde wykonane polecenie map jest pamiętane jedynie dopóki nie zamkniemy Vima. Aby temu zapobiec musimy dopisać wywołanie tego polecenia w pliku .vimrc. W tym celu otwieramy ów plik i dopisujemy:
map <skrót> <sekwencja>
Jeżeli chcemy wkleić zawartość rejestru a do treści pliku to wystarczy poprzedzić komendę p kombinacją "<litera_rejestru> czyli w naszym wypadku wciskamy "ap.
Nasza linijka powinna wyglądać mniej więcej tak:
map <C-t> c" ^]pa"^]

Na koniec kilka uwag:
  • Skrót <C-t> oznacza przytrzymanie przycisku Ctrl oraz wciśnięcie klawisza t,
  • Aby podejrzeć zawartość rejestrów bez wklejania można wywołać polecenie :registers,
  • Skrót <C-r> służący do wklejania zawartości rejestrów (i nie tylko ale o tym może innym razem) można wywołać także w trybie wprowadzania,
  • Do powtarzania tylko ostatniej czynność możemy użyć klawisza . (kropka) zamiast nagrywania makra, np. gdy chcemy po prostu wstawić jakiś tekst parę razy to wstawiamy go raz a potem . (kropka) będzie robić to za nas.

Zachęcam wszystkich do radosnego tworzenia własnych makr!

środa, 2 listopada 2011

Vim - ciekawsze pluginy

Jako, że na co dzień używam Vima w pracy i w domu to zdążyłem już wyrobić sobie opinie na temat różnych pluginów. Najciekawsze i jednocześnie najczęściej przeze mnie korzystane omówię poniżej:
  • Conque Shell
    Wtyczka, która czyni z Vima terminal. Najlepiej omówić to na przykładzie. Ja używałem bardzo często tego pluginu do integracji z klientem MySQLa, tzn. dzięki Conque Shell uruchamiamy z Vima wybrany program a jego wyjście pojawi się nam w oknie. Na tym co wyświetli program będziemy mogli operować tak jak na każdym pliku tekstowym. Co więcej, w trybie wprowadzania komunikujemy się z programem. W moim przypadku bardzo często przeklejałem całe SQLe i wyświetlał mi się wynik w postaci ładnej tabelki wewnątrz Vima.

    Tutaj widać 3 uruchomione procesy wewnątrz Vima (bash,top,ipython):

    Obsługa:
    • :ConqueTermSplit
      Powoduje uruchomienie procesu w oknie poniżej.
    • :ConqueTermVSplit
      Powoduje uruchomienie procesu w oknie z lewej.
    • <F9>
      Przekleja zaznaczony tekst do procesu ostatnio uruchomionego.

    Przykład: :ConqueTermSplit bash
  • vcscommand
    Bardzo fajny skrypt do integracji z wieloma systemami kontroli wersji (konkretnie CVS, SVN, SVK, git, bzr, hg). Sprawia on, że edytując dany plik nie musimy się zastanawiać czy aby zobaczyć jego historię musimy wywołać svn log czy git log, gdyż ten skrypt sam to sprawdzi i wywoła za nas. Oczywiście posiada on znacznie więcej opcji, chociażby wyświetlanie zmian za pomocą mechanizmu Vima (:VCSVimDiff <commit>).

    Obsługa:
    • :VCSUpdate
      Odpowiada komendzie svn update.
    • :VCSCommit
      Otwiera okno na wpisanie komentarza. Po zamknięciu okna następuje commit.
    • :VCSDiff <commit>
      Wypisuje zmiany aktualnej wersji w stosunku do podanej w argumencie w taki sam sposób jak svn diff (czy tam git diff itp.).
    • :VCSVimDiff <commit>
      Tak samo jak powyżej tyle, że zmiany są wyświetlana za pomocą wewnętrznego mechanizmu Vima.
    • inne analogiczne polecenia takie jak: VCSLog, VCSBlame itd.
  • The NERD tree
    Nieco ciekawszy plugin do poruszania się po katalogach i plikach. Od razu dodam, bo może nie każdy wie, że próbując otworzyć katalog do edycji w Vimie wyświetla się nam jego skład i możemy się po nim poruszać, np. :e .

    Ten plugin oferuje nam dodatkowe możliwości jak np. zakładki, tworzenie i usuwanie plików oraz katalogów, filtrowanie zawartości katalogów, sortowanie itd.

    O obsłudze nie będę pisał, ponieważ ten plugin podmienia standardową funkcjonalność listowania katalogu. O reszcie można poczytać w pomocy.
  • grep
    Prosty skrypt służący do grepowania (wyszukiwania) po plikach. Wywołuje on program grep z różnymi opcjami rozszerzając jego funkcjonalność o filtry. Tym właśnie różni się on od wbudowanego polecenia vimgrep.

    Podstawowa obsługa:
    • :Grep <ciąg_do_wyszukania> <pliki>
      Wyszukuje w danym katalogu. Jako <pliki> można wstawić *
    • :RGrep <ciąg_do_wyszukania> <pliki>
      Tak samo tylko, że wyszukuje również w podkatalogach.
    • Filtry czyli w jakich plikach nie ma wyszukiwać można ustawić w pliku .vimrc:
      let Grep_Skip_Dirs = '.svn CVS'
      let Grep_Skip_Files = '*.bak *~ *.swp tags *.vim'
  • SQLUtilities
    Fajna wtyczka do formatowania zapytań. Oczywiście potrafi ona znacznie więcej ale w gruncie rzeczy formatowanie jest najciekawsze i najbardziej przydatne.

    Podstawowa obsługa:
    • \sfs
      Powoduje, że zaznaczony kod SQL zostaje ładnie sformatowany.
  • comments
    Również bardzo prosty skrypt do komentowania kodu. Rozpoznaje on automatycznie typ pliku i komentuje używając odpowiednich dla pliku symboli.

    Obsługa:
    • <Ctrl-C>
      Komentuje linię bądź zaznaczenie.
    • <Ctrl-X>
      Odkomentowuje linię bądź zaznaczenie.
  • TagBar
    Fantastyczny plugin wyświetlający nam listę funkcji, klas, typów itp. w bocznym panelu.

    Obsługa:
    • <F9>
      Wyświetla bądź chowa panel boczny.
  • neocomplcache
    Kolejny świetny skrypt. Umożliwia on podpowiadania "w locie" przy pisaniu jak obserwujemy w różnych edytorach. Ten plugin naprawdę polecam!

    Co do obsługi to nie będę się również rozpisywał, najlepiej ze strony skryptu przekopiować domyślne ustawienia do pliku .vimrc i potestować.

piątek, 21 października 2011

Git i (współ)praca z innymi systemami kontroli wersji

Dzisiaj będzie bardziej konkretny post. Jako, że sam nie tak dawno zmieniałem pracę, w której niestety programiści używali cvsa i nie mieli ochoty tego zmieniać (zgodnie z zasadą po co coś zmieniać skoro działa) to postanowiłem opisać jak sobie poradzić w takiej sytuacji.


Po co?
Pytanie jak najbardziej zasadne. Odpowiedź jest prosta, po to aby móc korzystać z wszystkich zalet gita nie robiąc rewolucji wśród reszty zespołu. Jakie to zalety? Jeżeli ktoś nie wie do odsyłam do link lub do link. Mnie cieszy najbardziej to, że mam całe sklonowane repo tylko dla siebie i mogę sobie commitować ile mi się podoba. Nie bez znaczenia jest też łatwość tworzenia branchy i ich niemal automatyczne mergowanie.

Krok 1 - tworzenie centralnego repozytorium gita tylko do odczytu
Na początku musimy stworzyć repozytorium, które będzie klonem tego głównego (czy to cvs czy svn). Najlepiej postawić je na maszynie, do której wszyscy będą mieli łatwy dostęp. Natomiast, co ważne to repozytorium będzie tylko ściągało zmiany z oryginalnego, nie będziemy żadnych zmian bezpośrednio do niego wrzucać!

W przypadku cvsa robimy to tak:
Przed importem należy ustawić zmienną CVSROOT w ten sposób:
$ CVSROOT=:method:user@host:/path/to/CVS/root cvs login
Później tworzymy sobie jakiś katalog, w którym będzie znajdowało się repozytorium i przechodzimy do niego. I wreszcie importujemy wszystko z cvsa:
$ git-cvsimport -p x -v -d :method:user@host:/path/to/CVS/root/someproject
method oznacza typ serwera, np. pserver. Możemy wprowadzić kilka drobnych udogodnień:
$ git config cvsimport.module someproject
$ git config cvsimport.r cvs
$ git config cvsimport.d $CVSROOT
W ten sposób przy aktualizowaniu repozytorium nie będziemy musieli prawie nic podawać.

A w svnie tak:
Na początku tworzymy sobie katalog, gdzie chcemy trzymać repozytorium i przechodzimy do niego. Później wywołujemy:
$ git-svn init svn://host/trunk/someproject
$ git-svn fetch
$ git-svn rebase

Zaznaczam w tym miejscu, że jeżeli mamy jedno repozytorium z wieloma projektami to warto zrobić kilka repozytoriów gitowych - każdy per projekt. Stąd someproject w podanych wyżej wywołaniach.

Krok 2: sklonowanie repozytorium gita
Teraz klonujemy sobie świeżo utworzone repozytorium, na którym będziemy pracować:
$ git clone /path/to/git dir

UWAGA: pracujemy tylko na branchach. Zaraz po sklonowaniu warto utworzyć sobie jakiegoś brancha aby nie pracować na branchu master. Jest to ważne przy późniejszym przerzucaniu zmian. Brancha możemy utworzyć w ten sposób:
$ git checkout -b nazwa_brancha
Utworzymy w ten sposób brancha bazującego na branchu master.

Krok 3: wrzucanie zmian
Jak już popracowaliśmy to pora na commit. Robi się go w ten sposób:
$ git add zmienione_pliki
$ git commit
Listę zmienionych plików możemy podejrzeć w ten sposób:
$ git status
Ewentualnie aby nie dodawać do commita każdego pliku z osobna możemy użyć:
$ git commit -a

Krok 4: formatowanie patcha
OK, mamy wrzucone zmiany do lokalnego repo. Jeżeli zrobiliśmy tylko jednego commita to sprawa jest banalna:
$ git format-patch -1
W ten sposób git utworzy nam plik o nazwie 0001-commit_message.patch
Jeżeli zrobiliśmy więcej commitów to mamy dwa wyjścia:
  • jeżeli chcemy mieć w oryginalnym repozytorium widoczne wszystkie commity jakie zrobiliśmy w gicie to wywołujemy:
    $ git format-patch origin..HEAD
    To polecenie utworzy nam serię patchy, każdy per commit
  • jeżeli natomiast chcemy mieć jeden zbiorczy commit to musimy sobie zmergować zmiany. W tym celu wracamy na chwilę do brancha master:
    $ git checkout master
    Tworzymy sobie tymczasowego brancha:
    $ git checkout -b merge_branch
    I mergujemy:
    $ git merge --squash nazwa_brancha_ze_zmianami
    Opcja --squash powodują złączenie wszystkich commitów w jeden.
    Commitujemy:
    $ git commit
    Po czym tworzymy patcha:
    $ git format-patch -1
    Może teraz przejść na innego brancha i usunąć ten tymczasowy:
    $ git checkout master && git branch -d merge_branch

Krok 5: wrzucanie zmian do oryginalnego repozytorium
Gdy mamy już patcha lub patche to przenosimy je do katalogu z checkoutem czy to cvsa czy svna. Po czym wywołujemy:
$ patch -p1 < plik_patcha
Przeglądamy sobie czy wszystko jest OK i commitujemy jak zwykle.
Jest też sposób automatyczny, którego nie polecam. Lepiej mieć nad wszystkim kontrolę. Jeżeli ktoś chce to niech poszuka informacji o git-svn dcommit lub git cvsexportcommit.

Krok 6: aktualizacja centralnego repo gita
Przechodzimy do katalogu z naszym centralnym repo gita i wywołujemy:

Dla cvsa:
$ git cvsimport -a -p -x

Dla svna:
$ git-svn fetch
$ git-svn rebase

Krok 7: aktualizacja lokalnego repo gita
W katalogu z naszym lokalnym repo wykonujemy (na każdym branchu, który chcemy zaktualizować):
$ git pull origin master
Należy pamiętać, że aby uniknąć konfliktów przy nakładaniu patcha należy zawsze przed utworzeniem brancha tymczasowego zaktualizować brancha master jak również checkout originalnego repo.

I tak w kółko :)
Wydaje się, że to dużo roboty. Powiem tak, jak już się raz to wszystko zrobi to potem nie ma jej w cale więcej niż dotychczas (oczywiście poza przerzucaniem zmian). Na przykład aktualizowanie repozytoriów można zautomatyzować jakimś prostym skryptem. Korzyści płynące z pracy z gitem są bezcenne. Naprawdę warto spróbować.

wtorek, 18 października 2011

Własność intelektualna

Ostatnio słuchając audycji o tytułowej własności intelektualnej na kontestacji przyszła mi do głowy pewna myśl. Wyobraźmy sobie, że ktoś stworzył maszynkę, która potrafi kopiować dowolne przedmioty, np. wkładamy do niej jabłko, ona sobie tam chwilę pohałasuje i w efekcie dostajemy drugie dokładnie takie samo jabłko. Teraz wyobraźmy sobie, że kupując na straganie jedno jabłko sprzedawca każe nam podpisać umowę, w której zobowiązujemy się do nie kopiowania takiego jabłka. Wydaje się, że zdrowo myślący człowiek popukał by się w czoło i pomyślał "co mi tu będzie jakiś pe**ł mówił co ja mam robić ze swoim jabłkiem" po czym poszedłby do innego sprzedawcy. W efekcie nasz kochany sprzedawca nie wiele by zarobił.

Dlaczego więc w umowach (licencjach) dot. oprogramowania powszechnie akceptujemy takie zapisy? Odpowiedź jest dość prosta. Nie dość, że istnieje prawo chroniące takie umowy to jeszcze wszędzie wokół karmią nas stwierdzeniami typu, że nie moralnym jest godzić się na kopiowanie oprogramowania bez płacenia za to. Tak naprawdę nie o zapłatę tu chodzi. Wszechobecnym argumentem są ogromne straty, które są liczone w oparciu o błędne przekonanie, że gdyby nie kopiowanie to ci wszyscy ludzie by to oprogramowanie kupili. Ale przecież programów nie można traktować na równi z produkcją, dajmy na to, noży. Tworzenie oprogramowania to nie przemysł lecz usługi. I bardziej adekwatne byłoby w moim odczuciu płacenie za wykonaną usługę, tzn. usługę napisania programu. Jasne, że wówczas cena oprogramowania byłaby dużo droższa ale i to oprogramowanie byłoby znacznie łatwiej dostępne. Powiedzmy, że jeden człowiek nie byłby w stanie kupić sobie żadnego programu ale jak zrzuci się np. gmina, miasto, firma czy inna grupa ludzi to wszyscy mogą później z niego korzystać. Tak więc i programiści więcej zarobią, a i konsumenci więcej mieć będą a do tego jeszcze wytworzy się silna konkurencja więc również jakość oprogramowania wzrośnie.

Konkludując, uważam, że to nie żadne państwo powinno mówić jakie umowy są moralne a jakie nie, a już tym bardziej nie powinno niczego narzucać odgórnie (jak w przypadku praw autorskich). Zresztą widać dzisiaj, że mimo odgórnego zakazu ludzie i tak kopiują na potęgę. Nie wiem jak Wam ale dla mnie jest to sygnał, że konsumenci chcą jednak czegoś innego niż chce państwo. Niech zatem rynek (czyt. konsumenci) o tym zadecyduje!

poniedziałek, 17 października 2011

Niezastąpiony, niepokonany, najlepszy

Jak już wspominałem ostatnio kontynuuję cykl o tym jak stać się lepszym programistą. Na pierwszy ogień idzie zatem Vim. Na wstępie należałoby przede wszystkim zadać sobie pytanie "po co?". Po co odchodzić od przyjemnych, intuicyjnych edytorów (ba, całych IDE) na rzecz kompletnie nieporęcznego, nieintuicyjnego i minimalistycznego (tylko z wyglądu) Vima. Odpowiedzi mógłbym udzielić całe mnóstwo ale skupię się na tych najważniejszych. Przedstawię je opisując od razu każdą z nich z osobna.

Rozpocznę od flagowej idei, która przyświecała jeszcze staremu vi czyli ergonomia i wygoda pisania. W czasach kiedy pisanie czegoś na komputerze nie należało do rzeczy najprzyjemniejszych - głównie z uwagi na szybkość komputerów - powstał edytor zwany vi. Jak wieść niesie programiści prześcigali się w jak najmniejszej ilości naciśnięć kombinacji klawiszy aby wykonać dane zadanie (notabene dzisiaj podobna inicjatywa również istnieje, więcej informacji tutaj). Twórcy vi skupili się głównie na dwóch aspektach:
  • edycja i przeglądanie tekstu musi być jak najszybsze przy jak najmniejszym udziale człowieka,
  • komendy, skróty klawiszowe powinny być zaprojektowane w sposób jak najbardziej spójny, logiczny, łatwy do zapamiętania oraz rozłożone tak aby człowiek nie musiał odrywać dłoni od klawiatury (ani wyginać nadgarstków).
Specjalnie nie napiszę co takiego wymyślili, ponieważ tego dowiesz się w trakcie nauki (do której gorąco namawiam). Warto tutaj wspomnieć, że Vim jest kompletnie od nowa przepisanym starym vi i udoskonalonym - stąd nazwa Vi IMproved (co prawda na początku brzmiała Vi IMitation, ale jak tylko twórcy przepisali 99% funkcjonalności vi to zmienili nazwę). W tym miejscu mogę jeszcze nadmienić pomniejsze zalety, które pozostały po poprzedniku takie jak bardzo małe zużycie zasobów systemowych do tego stopnia, że można z powodzeniem pracować zdalnie na słabym łączu - dodam także, że Vim jest jednym z nielicznych edytorów, które potrafą z powodzeniem pracować z plikami, które zajmują więcej miejsca niż zainstalowana ilość RAMu w komputerze.

Stary vi to podstawa, która narzuca filozofię działania i logikę myślenia. Jednakże Vim oferuje znacznie więcej. Przede wszystkim czyni on z edytora swego rodzaju powłokę, tzn. jak z każdą powłoką komunikujemy się pewnym językiem skryptowym. Nie inaczej jest w tym przypadku, język skryptowy o którym mowa zwie się VimL (od Vim Language). Do czego on może służyć? Przede wszystkim do pisania pluginów ale nie tylko. Prostym przykładem, który mógłbym podać jest wypisanie w każdej linii kolejnej liczby (czyli prosta pętla i po sprawie) czy na przykład posortowanie zaznaczonego tekstu lub wczytanie wyjścia odpalonego programu zewnętrznego (tak, Vim potrafi odpalać programy konsolowe). W tym celu nie musimy ani zapisywać tego kodu (choć oczywiście możemy), ani go kompilować (język jest interpretowany) - wystarczy że go po prostu wpiszemy (nie chcę tutaj przedstawiać dokładnie jak to się robi, podpowiem tylko, że wygląda to podobnie jak w przypadku interaktywnych interpreterów). Na sam koniec wspomnę tylko o takich standardowych funkcjach jak zaznaczanie blokowe, wyrażenia regularne, podświetlanie składni, obsługa tagów, podpowiadanie, zintegrowana pomoc, obsługa okien, wielu schowków, buforów, możliwość definiowania prostych snippetów (właściwie abbreviation, mechanizm snippetów dostarczają pluginy) i tak można by wymieniać bez końca. Nie będę wypisywał wszystkich możliwości bo raz, że ich wszystkich nie poznałem (ciekawe czy jest to w ogóle możliwe), dwa że zajęło by to mnóstwo miejsca i trzy, że bym Cię, drogi czytelniku po prostu zanudził. Dlatego skupię się teraz na tym jak najszybciej się nie tylko nauczyć ale i pokochać Vima.

Każda dystrybucja przychodzi z programem (skryptem) o nazwie vimtutor. Po jego wykonaniu odpali nam się Vim z wczytanym samouczkiem, który należy czytać i wykonywać podane w nim ćwiczenia. Przerobienie samouczka zajmuje ok. 40 min ale należy go zrobić solidnie, tzn. jak napisane jest "nie używaj strzałek" to ich nie używaj. Naprawdę należy pozbyć się nawyków, które poznałeś do tej pory. Aby utrwalić sobie nabytą w ten sposób wiedzę używaj Vima gdzie tylko się da. Na początku będziesz pracować nieco wolniej ale później twoja produktywność wzrośnie kilkukrotnie!

Po samouczku warto poczytać pomoc, przejrzeć takie strony jak Vim Tips, poczytać parę książek (polecam "Hacking Vim"), poprzeglądać pluginy (tutaj) czy też zapisać się na listę dyskusyjną. Ale najważniejsze to po prostu go używać.

Właściwie mógłbym zakończyć ten przydługi wpis krótką sentencją używaną wśród vimowców:
Jeżeli musisz coś zrobić więcej niż dwa razy to powinieneś się zastanowić jak można to lepiej wykonać.
Jeżeli interesują Cię konkrety (co i jak zrobić, czego użyć itp.) to daj znać w komentarzu. Postaram się szybko odpowiedzieć, a jak będzie większy odzew to mogę utworzyć jakiegoś większego posta na ten temat. No to pozostaje mi tylko życzyć powodzenia!

piątek, 14 października 2011

Efektywność i jakość

Postanowiłem rozpocząć pewien cykl postów na temat jak wyróżniać się (pod względem umiejętności) wśród programistów. Genaralnie jak być jeśli nie najlepszym programistą w zespole to chociaż w ścisłej czołówce. Już na samym początku muszę obalić jeden krążący wokół mit. Nie wystarczy dużo czytać o nowych technologiach czy wzorcach ani też nie wystarczy tylko odpowiednio dużo programować. Tak naprawdę wszystko zależy od tego jak abstrakcyjnie potrafisz myśleć. Wiem, trudne słowo ale postaram się je nieco przybliżyć.

Należy zacząć od słowa abstrakcja, ponieważ nie wszyscy chyba to słowo rozumieją. Jest to jedna z najważniejszych rzeczy, gdyż jak niektórzy twierdzą, tylko ona tak naprawdę odróżnia nas od zwierząt. Zapytacie pewnie, jak to? Przecież chodzimy na dwóch nogach, mamy wyprostowaną postawę a co najważniejsze mamy jeszcze świadomość. Otóż są to rzeczy sporne bo jakby się uprzeć to i małpa czasami chodzi na dwóch nogach, a co do świadomości to nie sądzę aby człowiek miał na nią monopol. Wracając do abstrakcji, to dzięki niej możemy w ogóle cokolwiek tworzyć. Abstrakcja to inaczej oderwanie, uogólnienie. Myślenie abstrakcyjne pozwala nam więc uogólniać - wyciągać cechy wspólne, stałe, niezmienne. Po co? Po to by te rzeczy ze sobą łączyć, ulepszać, naśladować oraz tworzyć zupełnie nowe.

Jak ma się to do programowania? Cóż, abstrakcje w programowaniu to przede wszystkim typy danych. Tworzymy je po to aby ułatwiać sobie pracę, np:
  • skracać ilość kodu potrzebnego do napisania,
  • dbać o poprawność programu,
  • ułatwiać sobie dzięki nim późniejsze modyfikacje.
Oczywiście są też największym źródłem problemów. Począwszy od języków dynamicznych gdzie praktycznie nie mamy możliwości wymuszenia prawidłowego korzystania z nich po chociażby (niewłaściwe) rzutowania w innych językach.

Co cechuje dobrego programistę? Dobry programista przede wszystkim myśli typami. Można porównać to trochę do Matrixa gdzie jeden z bohaterów nie widział już tych wszystkich liczb i znaków na ekranie ale same kobiety i mężczyzn. Co to oznacza w praktyce? Np. pisząc tekst nie widzimy znaków - widzimy słowa, zdania paragrafy, linie itp. Czytając kod nie widzimy do, while, for, function, mov rax, 0xff ale widzimy pętle, funkcje, obiekty, instrukcje. Tworząc kod widzimy interakcje i powiązania między typami. Jeżeli w ten sposób funkcjonujesz to nie irytuje Cię poprawianie błędów, nie nudzisz się przy rysowaniu UMLi, z chęcią przeglądasz cudzy kod. Tak, to wszystko sprawia Ci ogromną przyjemność. Nie wystarczy więc być geekiem aby być dobrym programistą (choć większość programistów nawet geekami nie jest). Oprócz wiedzy jak działa komputer (czyli znajomości systemów operacyjnych, sieci, imperatywnych języków programowania i assemblera ) trzeba jeszcze umieć i być nieźle wprawionym w myśleniu abstrakcyjnym.

Jak więc się wprawić? Oto kilka rad:
  • Do pisania używaj vima - nauczysz się nie tylko jak szybko pisać i edytować tekst ale także widzieć obiekty w tekście i na nich działać. Szybko oznacza tu naprawdę szybko (o wiele szybciej niż chociażby w jakimś Visual Studio). Korzystanie z vima przypomina programowanie, np. wydajesz instrukcje (naciskając odpowiedni ciąg kombinacji klawiszy) usunięcia wszystkiego od miejsca, w którym znajduje się kursor do drugiego wystąpienia litery f.
  • Dokumenty twórz w LaTeXu - wówczas będziesz widział w tekście strony, rozdziały, różnego rodzaju bloki. Nauczysz się widzieć dokument patrząc na jego źródło. Ponadto twoje dokumenty będą wyglądały nieziemsko w porównaniu do tych z różnych pakietów office.
  • Naucz się kilku języków deklaratywnych (z rodziny funkcyjnych i logicznych) - tam praktycznie programuje się samymi typami.
  • Wreszcie staraj się używać jak najbardziej niskopoziomowych narzędzi, pisz skrypty, debuguj w gdb itp. - te narzędzia są znacznie szybsze od graficznych. Mam tu namyśli czas jaki Ty spędzisz nad zrobieniem czegoś, oczywiście gdy już się wprawisz. Zaoszczędzisz sporo czasu oraz wiele się nauczysz.

Myślę, że w kolejnych postach opiszę pokrótce powyższe punkty. Mam nadzieję, że zmotywuje to chociaż niektórych do pracy nad sobą :)

czwartek, 13 października 2011

XMonad i XMobar na wielu monitorach

Ostatnio dostałem w robocie drugi monitor. Nic nadzwyczajnego, ot takie zwykłe BenQ 22". Jako, że wszędzie, gdzie tylko jest taka możliwość używam XMonada jako menadżera okien więc i tym razem postanowiłem zobaczyć jak sobie radzi z wieloma monitorami. Muszę przyznać, że jest jeszcze lepiej (!) niż na jednym monitorze w przeciwieństwie do innych menadżerów jak Gnome czy KDE, gdzie działa to fatalnie (nigdy nie wiem, na którym monitorze wyświetli się okno i takie tam).

Oczywiście wszystko działa out of the box ale nie spodobała mi się jedna rzecz. Otóż mój wspaniały panel (XMobar) wyświetlał się tylko na jednym monitorze. Niestety nie znalazłem w sieci żadnego poradnika jak zrobić aby wyświetlał się na dwóch więc jakby ktoś też tego potrzebował to zamieszczam poniżej instrukcję jak to zrobić (jako, że znam Haskella to nie było to dla mnie wielkim problemem).

Po pierwsze musimy utworzyć dwie instancje XMobara ze wskazaniem, na którym monitorze ma się uruchomić. W tym celu edytujemy nasz xmonad.hs i dorzucamy do funkcji main takie 2 linijki:
xmproc <- spawnPipe "xmobar -x 0"
xmpro2 <- spawnPipe "xmobar -x 1 ~/.xmobarrc2"
Parametr x oznacza numer monitora zaczynając od 0. Ja akurat potrzebowałem aby każdy panel wyglądał nieco inaczej (głównie ze względu na program trayer, który ma się znaleźć tylko na jednym monitorze) więc druga instancja uruchamia się z innym plikiem konfiguracyjnym (.xmobarrc2). W drugim kroku musimy powiedzieć menadżerowi aby wysyłał takie same dane do dwóch paneli. Edytujemy więc w xmonad.hs funkcję logHook:
xmonad $ defaultConfig
                { manageHook = manageDocks <+> manageHook defaultConfig
                , layoutHook = avoidStruts $ smartBorders $ tabbed shrinkText myTabConfig ||| layoutHook defaultConfig
                , logHook = dynamicLogWithPP $ xmobarPP
                        { ppOutput = \s -> do
                              hPutStrLn xmproc s
                              hPutStrLn xmpro2 s
                        , ppTitle = xmobarColor "green" "" . shorten 150
                        }              
                , modMask = mod4Mask
                , terminal = "gnome-terminal"
                , keys = \c -> myKeys c `M.union` keys defaultConfig c
                }
Interesuje nas linijka przy logHook a dokładnie ppOutput. Nie ma tam nic nadzwyczajnego, po prostu przekazujemy dane do obydwu instancji XMobara. I to właściwie wszystko. Mój cały config do XMonada dostępny jest tutaj. A do XMobarów kolejno tutaj i tutaj.

środa, 12 października 2011

Philips SAA7130 pod linuxem

Dla odmiany po poprzednim poście napiszę coś technicznego jak na prawdziwego informatyka przystało. Nie tak dawno kupiłem sobie tani tuner tv (ok. 50 zł) oparty na chipset'cie philips saa7130. Jak tylko tuner doszedł pocztą postanowiłem go od razu wypróbować. Zamontowałem kartę w moim itx'ie, na którym miałem postawione ubuntu. Odpalam, system wykrył co prawda kartę prawidłowo ale w tvtime nie miałem ani obrazu ani dźwięku. Okazało się, że linux załadował odpowiedni sterownik ale nie ustawił odpowiedniego modelu karty i tunera. Niestety na liście kart i tunerów nie ma mojego modelu, gdyż producentem tej karty jest jakaś mała firma jak to zwykle bywa w przypadku wykorzystywania tanich chipsetów. Jeżeli ktoś ma podobną kartę, z którą nie może dojść do ładu to poniżej przedstawiam rozwiązanie jak sobie radzić z tego typu kartami pod linuxem.

Na wstępie powinniśmy się zaopatrzyć w program tvtime i tvtime-scanner. Pod ubuntu najłatwiej w ten sposób:
$ sudo apt-get install tvtime

Teraz tworzymy prosty skrypt, który będzie testował różne ustawienia modeli kart i tunerów automatycznie. W tym celu tworzymy plik, np. o nazwie skrypt.sh:
#!/bin/bash

echo -e "Program started\n" > out
for i in {52..152} ; do
    for g in 0 1 26 ; do
        echo "Card: $i, Tuner: $g"
        rmmod -w saa7134_alsa saa7134_dvb saa7134_empress saa7134
        `modprobe saa7134 card=$i tuner=$g`
        timeout -9 120 tvtime-scanner 2>&1 | while read f ; do
            if [[ $f =~ "Znaleziono kana" ]] ; then
                exit 2; //found!!
            fi

            if [[ $f =~ "Nie znaleziono tunera" ]] ; then
                exit 3; //No tuner found!
            fi
        done

        echo "code = $?";
        if [ $? -eq 2 ] ; then
            echo -e "Found Card = $i, Tuner = $g\n" >> out
        fi
    done
done
echo -e "Program ended\n" >> out
Linie 4 i 5 oznaczają numery kart i tunerów, które będziemy sprawdzać. Ja wpisałem akurat te wartości, które na liście kart (link) i tunerów (link) nazwami najbardziej odpowiadały mojej karcie. Kolejna ważna linijka 9 decyduje o czasie poszukiwania sygnału. Otóż tvtime-scanner skanuje całe pasmo w poszukiwaniu programów. Trwa to bardzo długo więc jakbyśmy mieli za każdym razem przeszukiwać całe pasmo to sprawdzenie wszystkich kombinacji kart i tunerów zajęłoby nam z miesiąc czasu. Dlatego też programem timeout ustawiamy limit długości trwania skanowania - w moim przypadku 120 sekund wystarczy, gdyż pierwszy program mam zaraz na początku pasma.

W kolejnym kroku zmieniamy uprawnienia do skryptu na wykonywalne:
$ chmod a+x skrypt.sh

I uruchamiamy go jako root:
$ sudo ./skrypt.sh

Skrypt będzie się wykonywał dość długo (parę godzin) więc warto go zostawić na noc. Gdy skrypt zakończy działanie przeglądamy plik o nazwie out, który się utworzył. Będzie on wyglądał mniej więcej tak:

Card: 49, Tuner: 0
ERROR: Removing 'saa7134_dvb': No such file or directory
ERROR: Removing 'saa7134_empress': No such file or directory
code = 3
Card: 49, Tuner: 1
ERROR: Removing 'saa7134_dvb': No such file or directory
ERROR: Removing 'saa7134_empress': No such file or directory
code = 3
Card: 49, Tuner: 26
ERROR: Removing 'saa7134_dvb': No such file or directory
ERROR: Removing 'saa7134_empress': No such file or directory
code = 3
Card: 50, Tuner: 0
ERROR: Removing 'saa7134_dvb': No such file or directory
ERROR: Removing 'saa7134_empress': No such file or directory
code = 0
Card: 50, Tuner: 1
ERROR: Removing 'saa7134_empress': No such file or directory
code = 2
Card: 50, Tuner: 26
ERROR: Removing 'saa7134_empress': No such file or directory
code = 2
Card: 51, Tuner: 0
ERROR: Removing 'saa7134_empress': No such file or directory
Killed
FATAL: Error running install command for saa7134
code = 0
Card: 51, Tuner: 1
ERROR: Removing 'saa7134_alsa': No such file or directory
ERROR: Removing 'saa7134_dvb': No such file or directory
ERROR: Removing 'saa7134_empress': No such file or directory
ERROR: Removing 'saa7134': Device or resource busy

Interesują nas właściwie tylko te linijki, które zawierają code = 2. Oznacza to, że złapano sygnał na tych ustawieniach sterownika więc praktycznie mamy zagwarantowany obraz. No to odczytujemy numer karty i tunera i wywołujemy (z roota):
$ rmmod -w saa7134_alsa saa7134_dvb saa7134_empress saa7134
$ modprobe saa7134 card=numer_karty tuner=numer_tunera

To, że mamy zagwarantowany obraz nie oznacza, że będziemy mieli dźwięk. Z dźwiękiem na tych kartach jest tak, że jest on analogowy (nie ma żadnego urządzenia w rodzaju karty dźwiękowej) więc należy bezpośrednio podłączyć kabelkiem wyjście audio na karcie do wejścia liniowego na karcie dźwiękowej. Niestety to nie wystarczy. Karty te mają dwa źródła dźwięku. Jednym jest sygnał z wejścia antenowego oczywiście a drugim sygnał z wejścia audio (na karcie są dwa złącza audio-in oraz audio-out). To powoduje, że sterownik musi umieć się pomiędzy nimi przełączać. Dlatego odpalamy tvtime (już z użytkownika) i patrzymy czy mamy obraz i dźwięk. Jak nie mamy to bierzemy następną parę karty i tunera z pliku gdzie code = 2 i powtarzamy powyższy krok aż do skutku. Jak już znajdziemy odpowiednie ustawienia to możemy je wpisać na stałe aby ustawiały się przy starcie systemu. W tym celu w pliku /etc/modules dopisujemy na końcu:
saa7134 card=numer_karty tuner=numer_tunera

Jakby komuś się nie chciało tego wszystkiego robić to może od razu sprawdzić moje ustawienia - karta 126 i tuner 1. Miłej zabawy!

O powodach i godności

Człowiek z natury jest leniwy (a może tylko programiści) i jak już zaczyna coś robić to dopiero wtedy kiedy solidnie zaczyna go coś irytować. Mówi się nawet, że najwięcej pieniędzy jest tam gdzie ludzie narzekają. Co by nie mówić musi to być prawdą przynajmniej w moim przypadku. W życiu denerwowało i nadal denerwuje wiele rzeczy ale ostatnia kampania wyborcza wprawiła mnie w nadzwyczaj nieprzyjemny stan. Oczywiście nie jedna kampania już była wcześniej i nie tylko w tej jednej kampanii człowiek nie mógł włączyć telewizji, radia czy przeczytać gazety bez wszechobecnych twarzy i jakże wymownych sentencji. Najczęściej pojawiającym się słowem tej kampanii (przynajmniej tym, które mnie wyprowadzało z błogiego stanu ignorancji) była godność i to w różnych formach: godna płaca, godne życie, bla bla bla. Niezrozumiałość tego słowa przez polityków wcale mnie nie dziwi i nawet to nagminne powtarzanie tych bzdur nie popchnęło mnie do napisania tego postu. Popchnęła mnie natomiast wiara w te słowa przez bądź co bądź bliskie mi osoby. Szukałem w internecie jakiejś sensownej definicji i niestety nie znalazłem więc postanowiłem przedstawić swoją.

Po pierwsze nie ma czegoś takiego jak godna płaca. Te słowa w żaden sposób się ze sobą nie łączą. Godność (a także i honor) jest bezpośrednio związana z innym bardzo ważnym słowem: wolność. Godność bowiem tracimy wówczas, gdy zyskujemy korzyści na odbieraniu wolności innych ludzi (oczywiście bez ich zgody). Pozwolę sobie przytoczyć w tym miejscu dobry przykład podany przez pana Janusza Korwin-Mikkego. Otóż człowiek, który zarabia 100zł i przymiera głodem może żyć godnie w przeciwieństwie do tego, który ma się dobrze a nie zarabia i bierze zasiłek socjalny od państwa. Dlaczego? Ponieważ ten pierwszy żyje z tego co sam zarobi, żyje na tyle na ile potrafi i swoje życie zawdzięcza tylko sobie. Ten drugi natomiast korzysta z pieniędzy brutalnie zabranych innym ludziom (w większości tym biednym, którzy zarabiają za dużo aby móc dostawać zasiłek). Godnie to znaczy żyć za swoje!


Politycy nie tylko kłamią odnośnie godności ale również upadlają ludzi. Jeżeli ktoś wmawia mi, że nie żyję godnie i nakłania mnie do brania zasiłku bo wówczas będę żył godnie a do tego szczyci się jeszcze tym, że te zasiłki podniesie to jest to dokładnie upodlenie ludzi!

A gdzie tu związek z wolnością? Jeżeli ktoś jeszcze tego jeszcze nie widzi to wyjaśnię to konkretnie. Zabieranie pieniędzy ludziom pod przymusem jest zabieraniem wolności, korzystanie z kradzionych pieniędzy czy innych kradzionych rzeczy jest niemoralne bo wówczas to Ty, drogi czytelniku, stajesz na szczycie tej piramidy. Innymi słowy to Ty jesteś zleceniodawcą ergo również Ty godzisz w wolność innych ludzi.

Wiem co powie na to większość ludzi. Powiedzą, że jak tu żyć godnie jak ci wredni bogaci kapitaliści ciągle obniżają im pensje a z czegoś trzeba wyżywić swoje rodziny. Twierdzić będą, że "bogacą się kosztem biednych ludzi". Otóż w normalnym wolnym świecie bogatym zostać można tylko w jeden sposób, po prostu robiąc coś dla innych. Na tym polega praca. Ludzie mają różne cele, różne ambicje, ideały, marzenia ale aby je spełniać potrzebują różnych rzeczy. Cała sztuka polega na tym aby dawać ludziom to czego oni potrzebują a w zamian dostając potrzebne nam rzeczy (np. pieniądze). Zatem ludzie nie bogacą się kosztem innych lecz pracując na rzecz innych. Jest to najlepszy sposób na godne życie.