niedziela, listopada 23, 2008

Cthulhu nonogram

Na stronie Unspeakable Vault of Doom http://www.macguff.fr/goomi/unspeakable/weblog.html znajduje się zagadka - nonogram:



Rozwiązanie jest proste - szczególnie gdy ma się program który jest w stanie to zrobić :-) Napisałem go na programowanie logiczne. Działa całkiem dobrze, rozwiązanie znajduje bardzo szybko.


| ?- cthulhu(R,W), nono(R,W,O), rysuj(O).
####################
#####.##########..##
#.######.####.###..#
######.....######..#
#####.......####..##
##.##.......########
####.........#####.#
####.........#######
...#....#....###....
...#..#.#....###....
.#..#.#.#....###..#.
###.#.......###..###
#.##.#......###.##.#
.#.###.#....#####.#.
.#.##...#..##.##..#.
####..#.#..#..##.###
#.##.##.##..#..###.#
...#..#.#.#.#..##...
..#.###.#.#.#..##...
..#....#.#.##..##...

O = [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,1,1],[1,0,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,0,0,1],[1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,0,0,1],[1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1],[1,1,0,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1],[1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1],[1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1],[0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,0,0,0,0],[0,0,0,1,0,0,1,0,1,0,0,0,0,1,1,1,0,0,0,0],[0,1,0,0,1,0,1,0,1,0,0,0,0,1,1,1,0,0,1,0],[1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1],[1,0,1,1,0,1,0,0,0,0,0,0,1,1,1,0,1,1,0,1],[0,1,0,1,1,1,0,1,0,0,0,0,1,1,1,1,1,0,1,0],[0,1,0,1,1,0,0,0,1,0,0,1,1,0,1,1,0,0,1,0],[1,1,1,1,0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,1],[1,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1,1,1,0,1],[0,0,0,1,0,0,1,0,1,0,1,0,1,0,0,1,1,0,0,0],[0,0,1,0,1,1,1,0,1,0,1,0,1,0,0,1,1,0,0,0],[0,0,1,0,0,0,0,1,0,1,0,1,1,0,0,1,1,0,0,0]]
R = [[20],[5,10,2],[1,6,4,3,1],[6,6,1],[5,4,2],[2,2,8],[4,5,1],[4,7],[1,1,3],[1,1,1,3],[1,1,1,1,3,1],[3,1,3,3],[1,2,1,3,2,1],[1,3,1,5,1],[1,2,1,2,2,1],[4,1,1,1,2,3],[1,2,2,2,1,3,1],[1,1,1,1,1,2],[1,3,1,1,1,2],[1,1,1,2,2]]
W = [[8,2,2],[2,5,2,3],[5,2,2,2,2],[10,6],[6,2,2,1],[1,2,2,1,1],[3,2,4],[3,1,1],[2,3,5],[3,1,1],[3,2],[4,2,1],[6,4,4],[2,11],[16],[11,7],[1,2,3,2,4],[1,3,2,2],[2,2,1,2,3],[8,2,2]] ? ;

(74 ms) no


Po przekształceniu rozwiązania na bitmapę:

Pierwsza wersja wikiTranslate - 0.0

Posiedziałem dzisiaj nieco nad wikiTranslate i stworzyłem jego pierwszą, testową wersję. Nie ma tego dużo - "wc -l *.hs" mówi o 44 linijkach. A tak na prawdę istotnych jest 16 z nich. Reszta to importy oraz moduł pomocniczy - pakiet "download-curl" nie chce się instalować na Windowsach więc napisałem mikro moduł który może go do pewnego stopnia zastąpić.

Wymagane pakiety: utf8-string, xml-light, tagsoup.
Zalecane: download-curl.

Ściągnąć z tej strony.

Krótka instrukcja.

1. Ściągamy i rozpakowujemy (wt.7z zawiera archiwa dla Linuxa i Windowsa)
2. Uruchamiamy program w konsoli podając jako parametry słowa do przetłumaczenia
3. Program wypisuje nam tłumaczenie na ekran

Wszystko działa fajnie tylko... pewne rzeczy są na stałe zaszyte w programie:
- język źródłowy: domyślnie polski ("pl"). Patrz "baseLanguge" w pliku wt.hs
- języki docelowe: ustawione na sztywno są nastepujące języki:
> filterLang = ["en","it","pt","de"]

Aby zmienić te stałe należy wyedytować plik wt.hs i zrekompilować program. Zakładam znajomość ghc - to jest wersja alpha więc nie osoby do których ma trafić ten post powinny potrafić sobie z tym poradzić :-)

TODO w kolejności priorytetów:
- wczytywanie języka źródłowego i języków docelowych z linii poleceń
- lepsze radzenie sobie z UTF8 - rosyjski wychodzi beznadziejnie
- WebUI
- radzenie sobie ze stronami "disambiguation"

Na deser wynik przykładowego zapytania
--- cut ---
[tener@tenserwer wikiTranslate]$ ./wt "Algorytm A*" dąb wikipedia polska wrocław "piłka nożna" "macierz odwracalna" "rachunek lambda"
Translations for term "Algorytm A*" in language "pl"
de A*-Algorithmus
en A* search algorithm
it A*
pt Algoritmo A*
------------------------------
Translations for term "dÂąb" in language "pl"
de Eichen
en Oak
it Quercus
pt Carvalho
------------------------------
Translations for term "wikipedia" in language "pl"
de Wikipedia
en Wikipedia
it Wikipedia
pt WikipĂŠdia
------------------------------
Translations for term "polska" in language "pl"
de Polen
en Poland
it Polonia
pt PolĂÂłnia
------------------------------
Translations for term "wrocÂław" in language "pl"
de Breslau
en WrocĂ
Âaw
it Breslavia
pt WrocĂ
Âaw
------------------------------
Translations for term "piÂłka noÂżna" in language "pl"
de FuĂÂball
en Association football
it Calcio (sport)
pt Futebol
------------------------------
Translations for term "macierz odwracalna" in language "pl"
de Reguläre Matrix
en Invertible matrix
it Matrice invertibile
pt Matriz inversa
------------------------------
Translations for term "rachunek lambda" in language "pl"
de Lambda-KalkĂÂźl
en Lambda calculus
it Lambda calcolo
pt CĂÂĄlculo lambda
------------------------------

--- cut ---

wtorek, listopada 11, 2008

I gdzie jest błąd?

Ostatnio na liście mailingowej haskell-cafe kilka razy padło pytanie o niedziałający z jakiegoś powodu program. W każdym z tych okazało się że problem leżał po stronie programisty :-)

Zadanie jest proste: uruchamiamy pewien proces, karmimy go danymi i oczekujemy od niego odpowiedzi. Naiwny kod wygląda tak:

> (p_stdin, p_stdout, p_stderr, p_handle) <- runInteractiveCommand jakas_komenda
> hPut p_stdin jakies_dane
> odpowiedz <- hGet p_stdout

Powyższy kod może, ale nie musi zadziałać. Co więcej: może raz działać dla danego programu, a raz nie działać. Prowadzi to do trudnych do debugowania i przez to irytujących błędów. Niestety: problem wynika z nieznajomości semantyki kanałów (ang. pipe - moje tłumaczenie jest chałupnicze) pomiędzy procesami.

Połączenia te mają ograniczoną pojemność "bufora". Po jego zapełnieniu proces który próbuje go "przepełnić" jest usypiany, aż bufor zostanie nieco opróżniony. Jest to (całkowicie słuszny) środek zaradczy przeciwko zużyciu przez nadgorliwy proces wszelkich zasobów systemu - w przeciwnym wypadku system musiałby przechować dowolnie dużo danych które zapisał dany proces. Bufor ten może być dość niewielki, np. 300 kb.

Jaki to ma związek z powyższym kodem? Ano taki, że w wyniku takiego właśnie działania kanałów kod ten powoduje często deadlocks - zakleszczenia.

Oto co się dzieje.

Uruchamiamy wysyłanie danych do naszego procesu:

> hPut p_stdin jakies_dane

Ale ów proces nie konsumuje ich na raz w całości. Zamiast tego zaczyna wysyłać częściowe porcje danych które wędrują do p_stdout. Wysyła ich na tyle dużo, że bufor p_stdout zapełnia się. Zostaje więc uśpiony. Aby został obudzony musimy odebrać z p_stdout porcję danych. Ale nie możemy tego zrobić - jeszcze nie skończyliśmy wysyłać mu danych na p_stdin!

Rozwiązanie jest proste: należy uruchomić wysyłanie danych w innym wątku:
> forkIO (hPut p_stdin jakies_dane)

W tym momencie jeden wątek będzie realizował wysyłanie danych, a drugi odbieranie. Co prawda jeden z nich może zostać uśpiony (bo np. proces nie odebrał jeszcze wszystkich danych i wykonuje teraz jakieś obliczenia) ale nie spowoduje to zakleszczenia.

Scenariusz może się jeszcze bardziej skomplikować, jeżeli interesuje nas równocześnie wyjście z p_stderr. W tym momencie ten kod także będzie błędny:

> (p_stdin, p_stdout, p_stderr, p_handle) <- runInteractiveCommand jakas_komenda
> hPut p_stdin jakies_dane
> odpowiedz <- hGet p_stdout
> odpowiedz_stderr <- hGet p_stderr

Dlaczego? Proces może zapełnić bufor p_stderr i zostać uśpiony zanim zamknie swoje standardowe wyjście (dzięki czemu wywołanie "hGet p_stdout" się skończyłoby się i zaczelibyśmy opróżniać p_stderr).

Rozwiązanie w tym przypadku jest nieco bardziej skomplikowane, jednak zaczyna się tutaj pojawiać pewien schemat:

> (p_stdin, p_stdout, p_stderr, p_handle) <- runInteractiveCommand jakas_komenda
> forkIO (hPut p_stdin jakies_dane)
> mv <- newEmptyMVar :: IO (MVar String)
> forkIO (hGet p_stdout >>= putMVar mv)
> odpowiedz_stderr <- hGet p_stderr
> odpowiedz <- takeMVar mv

(Dla opisu MVar przeczytaj ten post)

Co się tutaj wydarzyło? To co poprzednio: dodaliśmy nowy wątek który zajmuje się obsługą wejścia/wyjścia dla dokładnie jednego uchwytu (Handle).

Zauważmy, że w poprawnym kodzie mamy dokładnie jeden wątek dla jednego uchwytu: jeden "główny" oraz dwa utworzone przez forkIO. Jest to ogólna reguła by unikać tego typu zakleszczeń.

Uważny czytelnik zauważy, że w pewnym momencie odszedłem od słowa "kanał" (pipe) na korzyść słowa "uchwyt" (handle). O ile te pierwsze występują w przypadku komunikacji między procesami - i ten przypadek rozważamy - o tyle ten typ błędu występuje ogólnie dla typu uchwytów, które w GHC wykorzystywane są dla wielu typów operacji wejścia wyjścia - w szczególności dla połączeń sieciowych. W ich przypadku również może dochodzić do tego typu błędów.

W każdym z powyżej zacytowanych kawałków kodu jest czai się jeszcze jeden typ błędu, wynikający z semantyki uchwytów w GHC. Jak możemy przeczytać w dokumentacji nieużywany uchwyt jest automatycznie zamykany przez odśmiecacz (GC - garbage collector). Ma to ważną implikację: nie mamy gwarancji kiedy to nastąpi. Dlatego też może się zdarzyć, że otworzymy zbyt wiele plików na raz i system odmówi nam otwarcia nowych deskryptorów pliku. RTS wyrzuci nam w tym momencie wyjątek którego prawdopodobnie nie złapiemy - i nasz program zostanie zabity. Stąd ważny nawyk programistyczny: nieużywane uchwyty zamykamy tak szybko jak tylko przestają nam być potrzebne i nie liczymy w tym przypadku na pomoc systemu.

Dla kompletności oto poprawny (mam nadzieję...) kod:

> (p_stdin, p_stdout, p_stderr, p_handle) <- runInteractiveCommand jakas_komenda
> forkIO (hPut p_stdin jakies_dane >> hClose p_stdin)
> mv <- newEmptyMVar :: IO (MVar String)
> forkIO (hGet p_stdout >>= putMVar mv >> hClose p_stdout)
> odpowiedz_stderr <- hGet p_stderr
> odpowiedz <- takeMVar mv

niedziela, listopada 09, 2008

I na co tu się zdecydować?

Bardzo wielu programistów robi od czasu do czasu projekty "dla siebie". Nie ma w tym nic dziwnego: jest to okazja na stworzenie czegoś ciekawego i mniej lub bardziej użytecznego. Dla mnie najgorszym ograniczeniem w realizacji tego typu projektów jest czas: nie ma go na tyle by móc rozwijać wszystkie pomysły które przyjdą mi do głowy. Aktualnie mam ich kilka:

  1. MusicFS: Wirtualny system plików pod FUSE, odzwierciedlający kolekcję plików muzycznych. Podobne do biblioteki multimediów z foobar2000 czy WinAmpa. Za: potencjalnie przydatny, wykorzystanie FUSE, wykorzystanie baz danych (akurat na projekt z baz). Przeciw: nie potrzebuje tego tak bardzo, bo bibliotekę mam w foobar2000.

  2. SnapshotFS: podobnie jak wyżej wirtualny system plików pod FUSE. Prowadzi w tle kontrolę wersji, commity wykonywane w tle przy odmontowywaniu systemu, w wybranych odstępach czasu i na rządanie wysyłane przez dbus

  3. WikiTranslate: program tłumaczący słowa z użyciem Wikipedii. Wyszukujemy hasło spośród wpisów na zadanej wersji językowej Wikipedii a nastepnie patrzymy na linki odsyłające do wersji artykułu napisanej w innych językach. Za: teraz muszę robić ten proces ręcznie. Przeciw: dość skomplikowana heurystyka, nieprzyjemne parsowanie strony

  4. WikiTranslate-HAppS: interfejs webowy do WikiTranslate + cache. Za: możliwość udostępnienia narzędzia szerszej publiczności. Przeciw: trzeba najpierw napisać WikiTranslate, potrzebny jest serwer na którym można by to postawić

  5. UniNotifier: demon obserwujący wybrane strony www i feedy. Przy znaczącej modyfikacji wysyła informację. Za: bardzo przydatne. Przeciw: dla każdej strony trzebaby pisać z natury brzydki ekstraktor istotnych informacji


I za co mam się zabrać?

sobota, listopada 08, 2008

gitit - silnik wiki napisany w Haskellu

Dzisiaj na listę mailingową Haskell-Cafe trafiła wiadomość ogłaszająca wydanie wersji 0.2 programu o nazwie gitit. Jest to silnik wiki oparty o serwer HAppS, system kontroli wersji git, i bibliotekę do konwersji formatów znacznikowych pandoc.

Instalacja gitit z wykorzystaniem programu cabal-install jest bardzo prosta:
cabal update
cabal install pandoc -fhighlighting
cabal install gitit


A potem wystarczy już uruchomić go komendą
gitit
i połączyć się na http://localhost:5001

W trakcie instalacji ściągną się i skompilują automatycznie wszystkie potrzebne moduły. Niedawno gdy próbowałem zainstalować w podobny sposób samo HAppS okazało się, że pakiet ten wymaga pakietu unix, który z oczywistych względów nie zbuduje się zbyt łatwo pod Windowsem. Próbowałem tego dokonać z wykorzystaniem Cygwina - bezskutecznie.

Niestety także i teraz rozbiłem się o ten problem. Nie chciało mi się za bardzo przełączać na Linuxa tylko po to by zainstalować ten program. Na dobrą sprawę mogę przecież obejrzeć jego demo tutaj: http://johnmacfarlane.net:5001/.

Moje wrażenia? Jak na tak wczesną wersję zapowiada się całkiem ciekawie. Sprytnym posunięciem jest skorzystanie z gita do zarządzania wersjami: w końcu nie ma sensu wymyślać dwa razy koła, prawda? Chętnie zobaczę jak działać będą przyszłe wersje tej aplikacji.