sobota, sierpnia 01, 2009

Zamykamy podwoje

Zdecydowałem o zamknięciu tego bloga, na korzyść dwóch nowych, które zamierzam otworzyć.

Powodów jest kilka:

1. Ten blog miesza rzeczy osobiste (głównie pierwsze wpisy) z wpisami typowo technicznymi. Osoby zainteresowane jednymi niekoniecznie będą zainteresowane drugimi, i odwrotnie.
2. Jest on napisany po polsku, co uniemożliwia (ok, utrudnia) wielu anglojęzycznym czytelnikom dotarcie do mojego bloga. Nie oszukujmy się, niewielu jest w Polsce programistów zainteresowanych Haskellem...
3. Zmiana języka na angielski byłaby z kolei ciosem dla tych osób, które znają mnie osobiście i dla których czytanie o np. ostatniej wyprawie w góry jest bardziej naturalne w języku ojczystym. Tak jest mi z resztą wygodniej.
4. Wreszcie denerwuje mnie sam blogger: jest w nim mnóstwo niedociągnięć które utrudniają codzienne użytkowanie tej platformy. Z tego względu nowe blogi będą znajdowały się na serwisie wordpress.com

Bloga osobistego jeszcze nie założyłem.
Blog techniczny dostępny jest pod adresem http://mostlycode.wordpress.com

wtorek, marca 31, 2009

Odtwarzanie muzyki z linii komend

Bardzo często zdarza mi się odtwarzać muzykę mplayerem. Jest to odtwarzacz konsolowy, co sprawia pewne kłopoty. Brakuje mu także opcji odtwarzania wszystkich utworów z danego katalogu i podkatalogów.

Oto rozwiązanie tego problemu za pomocą narzędzia find:

$ find -type f -printf "`pwd`/%p\n" | shuf > /tmp/pls && mplayer -playlist /tmp/pls

Kilka uwag:
- używam komendy "shuf" aby zrandomizować listę odtwarzanych utworów. mplayer posiada opcję -shuffle, ale powoduje ona losowanie przy każdej zmianie utworu. shuf ustala jedną permutację listy, dzięki czemu można się cofnąć do odegranego już utworu
- lista tworzona jest w /tmp/pls. Można uprościć polecenie find jeżeli zapiszemy ją w aktualnym katalogu:

$ find -type f | shuf > pls && mplayer -playlist pls
Ja nie chcę jednak sobie zaśmiecać katalogów playlistami.

Można wreszcie nie używać pliku pomocniczego, a korzystać z przekierowania strumieni:

$ find -type f | shuf | mplayer -playlist -

Wtedy jednak nie można sterować mplayerem, np. zmniejszając głośność lub zmieniając utwór. W tym momencie shuf staje się zbędny.

$ find -type f | mplayer -shuffle -playlist -

Na dobrą sprawę możemy pominąć także parametr "-type f" który mówi find by wyświetlał jedynie pliki, a pomijał katalogi.

$ find | mplayer -shuffle -playlist -

W ostateczności można też słuchać plików w kolejności podawanej przez find:

$ find | mplayer -playlist -

I tak wygląda komenda od której zacząłem :-)

niedziela, lutego 22, 2009

Instancja klasy Show dla funkcji

Wbrew pozorom łatwo napisać taką instancję. Nie będzie ona spełniała warunku
> (read . show) == id
ale może być użyteczna. Można ją znaleźć w przypadku GHC 6.10 w module Text.Show.Functions. Wygląda ona tak:

instance Show (a -> b) where
     showsPrec _ _ = showString "<function>"

Dzisiaj wpadłem na nieco pożyteczniejszą formę. Niestety, wszystko ma swoją cenę - zadziała ona tylko dla funkcji których argumenty są elementami klasy Typeable.

instance (Typeable a, Typeable b) => Show (a -> b) where
     show _ = "<function: " ++ show (typeOf (undefined :: a)) ++ " -> " ++ show (typeOf (undefined :: b)) ++ ">"

Przykładowe działanie:
*Main> print print
Loading package syb ... linking ... done.
<function: () -> IO ()>
*Main> :set -Wall
*Main> print print

<interactive>:1:6:
     Warning: Defaulting the following constraint(s) to type `()'
         `Show a' arising from a use of `print' at <interactive>:1:6-10
         `Typeable a' arising from a use of `print' at <interactive>:1:0-10
     In the first argument of `print', namely `print'
     In a stmt of a 'do' expression: it <- print print
<function: () -> IO ()>
*Main> print putStrLn
<function: [Char] -> IO ()>
*Main> print id

<interactive>:1:0:
     Ambiguous type variable `a' in the constraint:
         `Typeable a' arising from a use of `print' at <interactive>:1:0-7
     Probable fix: add a type signature that fixes these type variable(s)
*Main> print (id :: Int -> Int)
<function: Int -> Int>
*Main> print (undefined :: Int -> Int -> Int)
<function: Int -> Int -> Int>

środa, lutego 11, 2009

Rozmowa o pracę

Stanowisko: informatyk / programista.

Tak, wiem, że to dość ogólne określenie. Nie mniej jednak ktokolwiek przychodzi na rozmowę kwalifikacyjną i wpisuje w CV znajomość C++ powinien oczekiwać, że poprosi się go o napisanie jakiegoś programu. Prostego. Na przykład liczącego n-ty element ciągu Fibonacciego. Prosty, banalny wręcz program.

Nie oczekuję wersji jedno-dwulinijkowej w Haskellu:

fib n = fibs !! n
fibs = 0 : 1 : (zipWith (+) fibs (tail fibs)) :: [Integer]

Nie oczekuję też rozwiązania powiązanego równania rekurencyjnego i napisania tej funkcji tak, by działała w czasie (praktycznie) stałym (zostawiam to zadanie czytelnikom).

Oczekuję prostej definicji w C. Ba, może być nawet taka, która ma złożoność wykładniczą!

int fib(int n)
{
    if (n==0) return 0;
    if (n==1) return 1;
    return fib(n-1) + fib(n-2);
}

Jak widać powyżej nie ma nawet sprawdzenia czy argument jest ujemny czy też nie. Pisanie powyższych kilku linijek przez pół godziny (i to bez powodzenia...) to strata czasu - obu stron.

wtorek, lutego 03, 2009

Przetestuj, jak TWOJA przeglądarka renderuje UTF-8

A tak na prawdę to tylko krótki programik do wypisania wszystkich znaków rozpoznawanych jako litery w kodowaniu UTF-8.

> module Main where
> import Prelude hiding ( writeFile )
> import System.IO.UTF8 ( writeFile )-- package 'utf8-string' on Hackage
> import Data.List.Split ( splitEvery ) -- package 'split' on Hackage
> import Data.Char ( isAlpha )
> import Data.List ( intercalate )
> main = writeFile "out.html" (header ++ chars ++ footer)
>         where
>         header = "<HTML><HEAD><META HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=utf-8\"></HEAD><BODY>"
>         chars = intercalate "\n<br>" . splitEvery 40 . filter isAlpha $ [ minBound .. maxBound ]
>         footer = "</BODY></HTML>"

Przykładowy wynik działania znajduje się tutaj. Treść tego posta jest jednocześnie kodem programu - wystarczy zaznaczyć całość, skopiować i zapisać z rozszerzeniem .lhs. Zrozumie go GHC, choć do uruchomienia będą potrzebne dodatkowe pakiety (patrz kod programu).

niedziela, lutego 01, 2009

Evil macro magic

Ostatnio w zobaczyłem ciekawy fragment kodu (C++):

#include "settings.hpp"
/* evil macro magic */
#undef _SETTINGS_HPP
#define extern
#include "settings.hpp"
#undef extern

Ciężko mi skomentować tak ciężkie nadużycie - które z drugiej strony jest genialne :-) Wyjaśnienie: powyższe linijki sprawiają, że wszystkie deklaracje "extern" zmiennych w "settings.hpp" stają się zwykłymi deklaracjami. Może to prowadzić do bardzo nieprzyjemnych błędów, ale zapewne czasami jest to użyteczne.

piątek, stycznia 30, 2009

Ciąg Fibonacciego

Chyba najkrótsza definicja jaką widziałem:

> import Control.Monad.Fix
> fibs :: [Integer]
> fibs = fix ((0:) . scanl (+) 1)

Haskell FTW!

środa, stycznia 07, 2009

Zabawy z Twitterem i GDB

Dzisiaj miałem potrzebę uruchomić serwer gry Killer Mud na debuggerze gdb: wczorajsze zmiany w kodzie spowodowały, że w ciągu doby serwer 3 razy się wywalił. Ponieważ chcę mieć możliwość debugowania kodu w przypadku jakichkolwiek problemów proces zostanie zamrożony. Sesja GDB uruchomiona jest spod screen'a, dzięki czemu nie muszę być zalogowany na serwerze by program mimo wszystko działał.

Tutaj jednak pojawia się problem: skąd mam wiedzieć, że program ciągle działa? Chciałbym dostawać jakieś powiadomienie w razie wystąpienia problemu. Poszperałem w paru źródłach, założyłem konto na Twitterze i ostatecznie stworzyłem taki oto plik .gdbinit:



define hook-run

set logging overwrite off
set logging file killer-gdb-log.txt
set logging on

end

define hook-stop

backtrace

shell rm -rf hook-stop-log.txt
shell tail -n 50 killer-gdb-log.txt >> hook-stop-log.txt
shell echo "----------------------" >> hook-stop-log.txt
shell tail -n 50 killer-run-log.txt >> hook-stop-log.txt

shell tar -czf killer-gdb-log.txt.tgz killer-gdb-log.txt
shell tar -czf killer-run-log.txt.tgz killer-run-log.txt

shell echo | mail -a killer-gdb-log.txt.tgz -a killer-run-log.txt.tgz -q "hook-stop-log.txt" -s "killer :: gdb :: hook-stop" ADRES@MAILOWY.COM
shell ~/tener/twidge update "killer :: gdb :: hook-stop"

end


define hook-continue

shell ~/tener/twidge update "killer :: gdb :: hook-continue"

end


Jak można się domyśleć, o zatrzymaniu muda wysyłane są powiadomienia na Twittera, podobnie o kontynuacji jego uruchomienia. Przy zatrzymaniu wysyłany jest także mail.

Stworzenie sesji jest dość proste:


screen
cd ~/mud/area
gdb ../src/rom
run 3000 >> killer-run-log.txt 2>&1


Teraz robimy tylko "Ctrl-a d" i mamy działającą w tle sesję GDB.

Garść linków:

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.

piątek, października 31, 2008

Czy polimorfizm w C# jest za słaby?

Polimorfizm jest bardzo przydatną cechą języków, która umożliwia skrócenie pisanego kodu, zwiększenie giętkości pisanych klas czy bibliotek, zwiększenie poziomu abstrakcji pisanego kodu - co w ostatecznym rozrachunku przekłada się na zmniejszenie nakładu pracy związanego z budową programu.

W C# mamy dostępne tzw. generics (link, link). Umożliwiają one np. tworzenie kontenerów dla obiektów dowolnego typu, podobnie jak wygląda to w C++:
[C#, 1]
Queue<int> kolejka_intow = new Queue<int>();

Wszystko wygląda fajnie, tylko że brakuje jednej rzeczy: możliwości "zaglądania" do obiektów polimorficznych. Nie możemy na przykład napisać funkcji:

public static T addGenerics<T>(T a, T b)
{
    return a + b;
}

Dlaczego? Kompilator słusznie wyrzuci nam:
Operator '+' cannot be applied to operands of type 'T' and 'T'

Z drugiej strony kod:

[C#, 2]
public static void testDoWithInstance()
{
     doWithInstance<int>(2);
     doWithInstance<float>(2);
     doWithInstance<string>("aaa");
     doWithInstance<int[]>(new int[] { 1, 2, 3 });
}

public static void doWithInstance<T>(T inst)
{
     System.Console.WriteLine(string.Format("<<{0}>>", inst));
}

Skompiluje się i wyświetli bez zarzutu:

<<2>>
<<2>>
<<aaa>>
<<System.Int32[]>>

Gdzie tkwi więc problem? Kod typu [2] skompiluje się, a w razie problemów (obiekt T nie może być przekształcony na napis) rzuci wyjątek. Oczywiście ktoś mógłby powiedzieć, że wszystko można jakoś wyświetlić, ale to nie jest sedno problemu. Sednem problemu jest to, że nie odkrycie czy dany obiekt obsługuje jakąś metodę odsuwamy do momentu wykonania programu. C# staje się więc w ten sposób efektywnie językiem dynamicznym.

Kod typu [1] natomiast wcale się nie będzie kompilować wcale. Kompilator nie wie z góry, czy obiekt typu T można dodać do drugiego obiektu typu T, innymi słowy czy obsługują one dodawanie. Można to obejść stosując mechanizm Reflection, który umożliwia analizowanie obiektów w trakcie wykonania programu. To rozwiązanie sprowadza się więc praktycznie do tego które mamy w [2], z tym, że nie możemy po prostu zrzutować danego obiektu na typ Object. Mechanizm Reflection ma dwie zasadnicze wady:
- jest dynamiczny, generuje wyjątki w trakcie działania programu, nie błędy kompilacji
- używanie go jest niewygodne i wymaga pisania dużych ilości dodatkowego kodu

Rozwiązaniem jest wprowadzenie kontekstów w postaci interfejsów do typów parametrycznych. Gwarantowałyby one implementację zadanych metod. Przykładowo:

[C#, 3]
public static T f<T implementing IPlus, IMinus>( T a, T b)
{
     return (a + a - b);
}
Z tego co się orientuje, nie ma jeszcze tego typu rozszerzenia w C#. A może się mylę?

sobota, października 25, 2008

Wywoływanie funkcji w C z języka C#

Udało mi się, po paru próbach, dojść do takiego kodu:

[C#]
       [DllImport("mytest.dll", EntryPoint="makeFFT")]
       private static extern void makeFFT_(IntPtr inputArray, IntPtr outputArray);

       public static void makeFFT(float[] inpArr, float[] outArr)
       {
            IntPtr inpArrBuf = Marshal.AllocHGlobal(sizeof(float) * inpArr.Length);
            IntPtr outArrBuf = Marshal.AllocHGlobal(sizeof(float) * outArr.Length);
            
            Marshal.Copy(inpArr, 0, inpArrBuf, inpArr.Length);

            makeFFT_(inpArrBuf, outArrBuf);

            Marshal.Copy(outArrBuf, outArr, 0, outArr.Length);
            
            Marshal.FreeHGlobal(inpArrBuf);
            Marshal.FreeHGlobal(outArrBuf);
       }

Gdzie funkcja w C ma typ:
[C]
void makeFFT( float* inArr, float* outArr )

Kod, choć niezbyt piękny, działa i robi to o co od niego oczekiwałem: wywołuje zawartą w bibliotece funkcję makeFFT gdzie inArr jest parametrem wejściowym, zaś outArr - wyjściowym. Zasadnicze pytanie brzmi: czy da się to zrobić krócej, bardziej elegancko?

niedziela, października 19, 2008

Prosty szablon wizualizacji

Wielu osobom wydaje się, że Haskell nie nadaje się do pisania czegokolwiek poza interpreterem innego języka :-) Nic bardziej mylnego! Jest to język ogólnego zastosowania, a przy tym jest on bardzo elegancki - znacznie bardziej niż ubogi w abstrakcje C czy zatłoczony składnią C++. No dobra, wystarczająco wkurzyłem fanów tych języków, czas zabrać się za konkrety ;-)

Zbudujemy dzisiaj w Haskellu prostą aplikację wykorzystującą OpenGL. Nie chcę tutaj pokazywać tajników programowania w OpenGL, od tego są inne strony. Będziemy potrzebować:


Zbudowanie ich wymaga odpowiednich plików nagłówkowych, ale to jest już mniej ciekawe zagadnienie.


  1. Nazwanie modułu

  2. Warto jest nazwać jakoś nasz program. Umieszczamy więc w pierwszej linii:
    >>> module Main where

  3. Importowanie odpowiednich bibliotek.

  4. Potrzebować będziemy OpenGL i GLFW (do robienia okienek i obsługi klawiatury):
    >>> import Graphics.Rendering.OpenGL -- lots of modules re-exported
    >>> import Graphics.UI.GLFW -- for window creation etc.

    Przydadzą się też biblioteka ułatwiająca programowanie wielowątkowe:
    >>> import Control.Concurrent
    Biblioteka do monad też się przyda:
    >>> import Control.Monad
    Z kolei ta biblioteka udostępnia funkcję printf, podobną do tej znanej z C:
    >>> import Text.Printf

  5. Inicjalizacja GLFW

  6. Początek funkcji main może wyglądać tak:
    >>> main = do
    >>>    initOk <- initialize -- [1]
    >>>    if not inikOk then error "Błąd inicjalizacji GLFW!" else return () -- [2]
    >>>     vmodes <- get videoModes -- [3]
    >>>     let bestVM = last vmodes -- [4]
    >>>     setWindowModeOk <- openWindow (videoModeToSize bestVM) [videoModeToRGBBits bestVM] FullScreen -- [5]
    >>>     if not setWindowModeOk then error "Nie udało się otworzyć okna" else return () -- [6]

    W linijce [1] próbujemy zainicjować GLFW. Funkcja ta zwraca wartość logiczną, którą sprawdzamy w [2], jeżeli nie udało się (ma ona wartość False) przerywamy działanie programu za pomocą funkcji error. Po pobraniu informacji o dostępnych rozdzielczościach ([3]) i wybraniu potencjalnie najlepszej z nich ([4]) robimy znów podobną rzecz: próbujemy otworzyć okno w tej rozdzielczości ([5]), jeżeli nam się nie powiedzie przerywamy działanie programu ([6]). Korzystamy przy tym z funkcji pomocniczych, o których opowiem za chwile.

    Niby działa, ale... można to napisać lepiej.

    >>> main = do
    >>>     tryTo initialize (error "Błąd inicjalizacji GLFW!")
    >>>     bestVM <- last `fmap` get videoModes
    >>>     tryTo (openWindow (videoModeToSize bestVM) [videoModeToRGBBits bestVM] FullScreen)
    >>>       (error "Nie udało się otworzyć okna")

    Co się zmieniło? Dwie rzeczy:
    - wprowadziliśmy funkcję tryTo
    - użyliśmy funkcji fmap
    Definicja funkcji tryTo jest prosta:

    >>> tryTo :: IO Bool -> IO () -> IO ()
    >>> tryTo doAction actionIfFailed = do
    >>>     b <- doAction
    >>>     if not b then actionIfFailed else return ()

    Przy pomocy funkcji when i rezygnując z niepotrzebnego cukru syntaktycznego można tą definicje istotnie skrócić:
    >>> tryTo doAction actionIfFailed = doAction >>= \b -> when (not b) actionIfFailed
    Nieco nieczytelnie można to zapisać jako:
    >>> tryTo doAction actionIfFailed = doAction >>= (flip unless) actionIfFailed
    Ale to już jest lekka przesada :-)

    Pozostaje jeszcze sprawa funkcji fmap. Pozwala ona nakarmienie funkcji argumentem pochodzącym z monady (tak na prawdę jej definicja jest nieco ogólniejsza, ale to nieistotne). Możliwa jej definicja w kontekście monad jest taka:
    >>> fmap pureFunc monadAction = monadAction >>= \ v -> return (pureFunc v)

    Dzięki tym dwóm funkcjom kod został skrócony i stał się bardziej przejrzysty. Przejdźmy więc nieco dalej.

  7. Funkcje pomocnicze

  8. W kodzie powyżej wykorzystałem dwie funkcje o następujących definicjach:
    videoModeToSize vm = Size (fromIntegral $ videoWidth vm) (fromIntegral $ videoHeight vm)
    videoModeToRGBBits vm = DisplayRGBBits (videoRedBits vm) (videoGreenBits vm) (videoBlueBits vm)
    Są to swego rodzaju funkcje "rzutowania", czy też "ekstrakcji": z trybu wideo (vm :: VideoMode) otrzymujemy za pomocą pierwszej funkcji rozmiar okna, zaś za pomocą drugiej - liczbę bitów poszczególnych kolorów. Nie jest to parametr, który koniecznie musimy podać, jednak pozwala on wymusić pewne zachowanie.

  9. Dalsze ustawianie środowiska

  10. Skoro mamy już okno, warto by nadać mu tytuł
    >>> windowTitle $= "My first Haskell + OpenGL app"
    Co się wydażyło w tej linijce?
    Skorzystaliśmy z funkcji $= do ustawienia parametru windowTitle. Jest to funkcja implementowana przez typy należące do klasy HasSetter. W większości popularnych języków programowania (C++, C#, Delphi, Ruby...) mamy możliwość tworzenia parametrów, choć różnie się nazywa w tych językach te mechanizmy. Zasadniczo chodzi o pewien parametr klasy, który można ustawić na pewną wartość. W momencie wykonania przypisania nie jest jednak wprost zapisywana w pamięci pewna wartość, lecz wykonywana jest odpowiednia metoda klasy z argumentem będącym wartością "przypisywaną" parametrowi. Jest to mechanizm wygodny i użyteczny, o czym świadczy chociażby fakt szerokiego wsparcia w językach. W Haskellu możemy ten mechanizm zaimplementować sami i - co więcej - jest to bardzo proste. Po szczegóły odsyłam do źródeł.

    W podobny sposób ustawimy callbacki (brrr co za słowo) do reagowania na zdarzenia środowiska:

    >>> keyCallback $= myKeyCallback
    >>> charCallback $= myCharCallback

    Gdzie myKeyCallback i myCharCallback to funkcje:

    >>> -- wywoływany przy naciśnięcu jakiegokolwiek klawisza
    >>> myKeyCallback key Press = do
    >>>    case key of
    >>>      SpecialKey ENTER -> restoreWindow
    >>>      SpecialKey ESC -> iconifyWindow
    >>>      _ -> return ()
    >>> myKeyCallback _ _ _ = return ()

    >>> -- wywoływany przy naciśnięcia klawisza, któremu odpowiada jakiś znak
    >>> myCharCallback chr st = putStrLn (printf "Znak '%c' został %s." chr (if st == Press then "naciśnięty" else "puszczony"))

    myKeyCallback reaguje na naciśnięcie entera i klawisza escape: jedno z nich chowa okno, drugi je pokazuje. Z kolei myCharCallback wypisuje informacje o tym, że ktoś nacisnął lub puścił jakiś klawisz któremu odpowiada pewien znak. Można zawrzeć funkcjonalność myCharCallback w myKeyCallback, ale nie byłoby to wygodne.

    Zmienimy jeszcze rozmiar punktu:
    >>> pointSize $= 4.0

    Nieco inaczej wygląda ustawienie dwóch innych parametrów:

    >>> enableSpecial KeyRepeat -- powtarzanie klawiszy
    >>> enableSpecial MouseCursor -- widoczny kursor myszy
    >>> --enableSpecial AutoPollEvent -- automatyczne pobieranie nowych eventów [*]

    Linia [*] jest wykomentowana, gdyż nie chcemy tak na prawdę tej funkcjonalności od GLFW. Jeżeli jest ona włączona to eventy pobierane są automatycznie przy wywołaniu funkcji swapBuffers. Ponieważ czasami wyświetlamy niewiele klatek na sekundę, nasza aplikacja zachowywałaby się w takich momentach nieprzyjemnie. Zamiast tego uruchomimy w tym celu oddzielny wątek:

    >>> forkIO (forever pollEvents)

    Zostanie on automatycznie zabity przy końcu działania programu.

    Chcielibyśmy też ustawić jakieś opcje widoku. Nie będziemy wymyślni, wystarczy nam ortogonalny widok "2D":

    >>> let Size sizeX sizeY = videoModeToSize bestVM
    >>> ortho2D 0 (fromIntegral sizeX) 0 (fromIntegral sizeY)

    Użycie funckji fromIntegral jest w paru miejscach konieczne, by zmienić typ z jednego całkowitoliczbowego na jakiś inny typ liczbowy (np. Int -> Double, GLint -> Int).

  11. Główna pętla renderingu

  12. Jesteśmy teraz gotowi na wywołanie pętli rysowania. Robi się to tak:
    >>> mainLoop
    ;-)

    Oczywiście musimy podać definicję mainLoop:
    >>> mainLoop :: IO ()
    >>> mainLoop = do
    >>>     (Position px py) <- get mousePos
    >>>     clear [ColorBuffer]
    >>>     let gray = Color3 0.5 0.5 0.5 :: Color3 Float
    >>>     let myPoint = (Vertex2 (fromIntegral px) (fromIntegral py)) :: Vertex2 Float
    >>>     renderPrimitive Points $ color gray >> vertex myPoint
    >>>     swapBuffers
    >>>     mainLoop

    Rysujemy więc w aktualnym położeniu kursora duży (ustawiliśmy to przed chwilą) szary punkt.

  13. A jak stąd wyjść?

  14. Wszystko wygląda fajnie, ale czasami chcemy wyjść z programu, prawda? Aby to zrealizować wykorzystamy pewien rodzaj zmiennych - MVar - których obsługę znajdziemy w module Control.Concurrent (a dokładniej tutaj).

    Obiekt typu MVar Int jest "pudełkiem" które może być albo puste, albo przechowywać obiekt typu Int. Stworzymy więc nową, pustą zmienną MVar która gdy zostanie zapełniona będzie sygnalizować konieczność zakończenia aplikacji. Proste, prawda?

    Do funkcji main dopisujemy przed wywołaniem mainLoop jedną linijkę:
    >>> quitMVar <- newEmptyMVar :: IO (MVar ())
    Teraz quitMVar będzie pudełkiem w którym może znaleść się wartość typu () - jest to typ o jednym elemencie, który także wygląda jak () - jest to krotka o zerze elementów:
    ()
    -- nie ma krotek jednoelementowych: (1)
    (1,2)
    (1,2,3)
    (1,2,3,4)
    (1,2,3,4,5)

    Nie potrzebujemy zatrudniać tutaj typu Bool czy też Int, więc nie robimy tego.

    quitMVar przekazujemy teraz do zmodyfikowanych wersji myKeyCallback i mainLoop:

    >>> keyCallback $= myKeyCallback quitMVar

    >>> mainLoop quitMVar

    Definicje te wyglądają tak:

    >>> -- wywoływany przy naciśnięcu jakiegokolwiek klawisza
    >>> myKeyCallback quitter key Press = do
    >>>    case key of
    >>>      SpecialKey BACKSPACE -> iconifyWindow
    >>>      SpecialKey ENTER -> restoreWindow
    >>>      SpecialKey ESC -> putMVar quitter () -- umieszczamy wartość w "pudełku"
    >>>      _ -> return ()
    >>> myKeyCallback _ _ _ = return ()

    >>> mainLoop :: MVar () -> IO ()
    >>> mainLoop qMV = do
    >>>      (Position px py) <- get mousePos
    >>>      clear [ColorBuffer]
    >>>      let gray = Color3 0.5 0.5 0.5 :: Color3 Float
    >>>      let myPoint = (Vertex2 (fromIntegral px) (fromIntegral py)) :: Vertex2 Float
    >>>      renderPrimitive Points $ color gray >> vertex myPoint
    >>>      swapBuffers
    >>>      tryTo (not `fmap` isEmptyMVar qMV) (mainLoop qMV) -- jeżeli qMV jest pusta, to kontynuujemy

  15. Sprzątamy po sobie...


  16. Kiedy wyszliśmy już z pętli renderingu nie zostało wiele do zrobienia. Zamykamy okno i idziemy spać:

    >>> closeWindow


Choć post ten wyszedł na znacznie dłuższy niż początkowo zamierzałem, sam kod jest zwarty i niewielki. Można go obejrzeć tutaj i tutaj. Kod źródłowy dostępny do ściągnięcia tutaj.

Mam nadzieję, że post wyszedł choć trochę ciekawie :-)

Zadania dla czytelników:
Znaleźć błąd w programie - łatwo go zauważyć po jego uruchomieniu

sobota, października 18, 2008

Haskell + OpenGL

Krótki szablon jak w Haskellu zapisać zrzut ekranu renderowanego przez OpenGL.
Plik zapisywany jest do formatu PAM, można go potem przekonwertować do dowolnego innego formatu rastrowego, np. za pomocą narzędzia convert z pakietu ImageMagick. Można też skorzystać z pakietu Netpbm.

Link na hpaste.org
Link na moim serwerku

sobota, lipca 26, 2008

libgmail - aby zaoszczędzić klikania

Byłem dzisiaj w górach. Jak zwykle po takiej wyprawie przychodzi czas dzielenia się zdjęciami. Obrobiwszy co potrzeba zostało mi do wysłania 71 MB. Spakować się tego za bardzo nie da, na płytkę pakować nie opłaca się... Zdecydowałem się wysłać to wszystko mailem. Ponieważ jednak przesyłanie kilkuste plików jest mocno niepraktyczne, spakowałem wszystko w kilka archiwów po 15 MB:

> 7z a rudawy.7z -mx=0 -v15mb *.jpg

Świetnie, mam teraz tylko 5 plików do wysłania. Kto jednak kiedyś wysyłał duże pliki przez maila doskonale wie, że wysyłają się one okropnie długo i często nie sposób jest wysłać wszystkiego na raz - trzeba cierpliwie plik po pliku ładować je na konto.
Ale! Od czego są biblioteki? Komenda

> yum search gmail

zdradziła mi, że Fedora oferuje między innymi następujący pakiet:

> python-libgmail.noarch : Library to provide access to Gmail via Python

Zainstalowałem, poszperałem nieco w dokumentacji i teraz pliki radośnie wgrały się na moje konto:

>>> import libgmail
>>> import getpass
>>> import glob
>>> ga = libgmail.GmailAcount(getpass.getpass("Podaj konto"), getpass.getpass("Podaj hasło"))
>>> ga.login()
>>> _7z = glob.glob("*7z")
>>> _7z
['rudawy.7z.003', 'rudawy.7z.001', 'rudawy.7z.004', 'rudawy.7z.005', 'rudawy.7z.002']
>>> map( lambda x: ga.storeFile( x, "rudawy" ), _7z )

I po sprawie :-)

piątek, lipca 18, 2008

Wyrafinowany sposób na wyjątek

Właśnie drogą eksperymentu doszedłem, jak zapętlić GHC... ale tak, by się zorientował i rzucił wyjątkiem :-)

Wystarczy nam taki kod:

> {-# OPTIONS_GHC -XRecursiveDo #-}
>
> module Main where
>
> import Control.Concurrent
> import Control.Monad.Fix
>
> loopChan :: IO (Chan ())
> loopChan = mdo chan <- dupChan chan
> return chan
>
> main = do
> c <- loopChan
> writeChan c ()

Kiedy spróbujemy go załadować do GHCi:

> Prelude> :l loop_channel.hs
> [1 of 1] Compiling LC ( loop_channel.hs, interpreted )
> Ok, modules loaded: LC.
> *LC> :type loopChan
> loopChan :: IO (Chan ())
> *LC> c <- loopChan
> *** Exception: <>

Lub wykonać:
> [Tener@laptener loop_chan]$ ghc --make loop_channel.hs
> [1 of 1] Compiling Main ( loop_channel.hs, loop_channel.o )
> Linking loop_channel ...
> [Tener@laptener loop_chan]$ ./loop_channel
> loop_channel: <>


To przytrafi nam się to samo: dostajemy wyjątek NonTermination. Co stanie się przy próbie uruchomienia tego kodu było dla mnie zagadką, gdy go pisałem - rzucenie tego wyjątku wydaje się być rozsądnym rozwiązaniem.

wtorek, lipca 01, 2008

Założenie o zamkniętości świata

Po angielsku: Closed World Assumption, w skrócie CWA. Jego treść jest prosta: "To, czego nie potrafimy udowodnić, jest fałszywe".

Naukowcy wydają się być bardzo wybiórczy w stosowaniu tego założenia. Wielokrotnie odrzucano przedwcześnie teorie, które później okazywały się prawdziwe. Z drugiej strony wiele jest niepotwierdzonych teorii, które mają wielu zwolenników, choć nie ma do nich zbyt uczciwych przesłanek (patrz tutaj).

Powiedziawszy to, zastanówmy się nad hipotezą:

Hipoteza: Bóg istnieje.

Wielu naukowców ma jasno sprecyzowany pogląd na temat prawdziwości tej hipotezy. Co ciekawe, bardzo często ci z nich, którzy negują jej prawdziwość, pytają się "Jak to możliwe, by jakikolwiek naukowiec wierzył w istnienie Boga?". Ich najczęstszym argumentem za tym, że Bóg nie istnieje jest brak dowodów na jego istnienie. Niejawnie korzystają więc oni z CWA. A należy przecież pamiętać, że jest to tylko założenie - które w prawdziwym świecie nie jest prawdziwe! Czy to dowodzi istnienia Boga? Oczywiście nie. Myślę jednak, że wyjaśniłem moje poglądy na temat "dowodów przez brak dowodów na istnienie".

Osobiście uważam że Bóg istnieje i że jest Jego wolą, by przez cały okres istnienia świata nie powstał żaden trwały dowód na Jego istnienie. (Przez trwały rozumiem tutaj taki dowód, który można powtórzyć w dowolnym momencie wobec dowolnej osoby.)
Dlaczego? Istnienie dowodu trwałego przekreślałoby istotę wiary. Jaki jest sens wiary w coś, co można udowodnić?

sobota, maja 10, 2008

Gra w ósemkę

Znana jest gra logiczna w "Piętnastkę" - na planszy 4x4 jedno pole jest puste, pozostałe są ponumerowane liczbami od 1 do 15. Należy wychodząc z konfiguracji początkowej dojść do ustawienia:

1 | 2 | 3 | 4
5 | 6 | 7 | 8
9 | 10 | 11 | 12
13 | 14 | 15 | X

Zagadka jest dość trudna obliczeniowo, dlatego na pracowni z Programowania Logicznego rozwiązujemy prostszą wersję - grę w "Ósemkę", której zasady są analogiczne, lecz wszystko rozgrywa się na planszy 3x3. Jakież było moje zdziwienie, gdy okazało się, że przechodzi bardzo niekonstruktywne i krótkie rozwiązanie:

osemka( _, _, _ ).

A wszystko przez błąd sprawdzaczki :-)

Zabieram się do pisania rozwiązania zgodnego z treścią zadania.

piątek, kwietnia 11, 2008

Hardware & Software

Czyli po polsku sprzęt i oprogramowanie. Ostatnio, w styczniu 2008, kupiłem laptopa. Mówiąc krótko:
Aristo Prestige 1700, GeForce 8600M GS, 2Gb RAM, Core 2 Duo 7500, 250 Gb HDD, matryca 15 cali glare.
Zadowolony? Jak najbardziej. Korzystałem okazjonalnie z innych laptopów; w tym podoba mi się szczególnie:
- wygodna klawiatura, co w laptopach bywa zmorą
- wytrzymała, niewycierająca się obudowa
- duży dysk twardy
- niezła karta graficzna, choć nie jest to GT to i tak Wiedźmin śmiga.
- bezawaryjna jak do tej pory praca
- porządna (jak na moje wymagania) karta dźwiękowa

Sprzęt nie jest jednak najważniejszy, o wiele więcej potęgi drzemie w oprogramowaniu.
Systemy:
- Fedora 8, podstawowy system
- Windows XP Home Edition, głównie do gier i AutoCada (którego nie używam ja, tylko K., a ona robi to sporadycznie).

Windowsa mam bez graficznych wodotrysków w stylu CrystalXP. Wyłączony jest nawet domyślny motyw XP na rzecz ascetycznego, szarego z Windowsa 2000. Powód? Wszystko to blaknie i wygląda niepoważnie w porównaniu z Compizem (Vista też). W zamierzeniu ma to być system zapasowy, ale dobrze skonfigurowany. Stąd obecność interpretera pythona, cygwina, Visual Studio 2008 (z MSDN'a), Total Commandera, Emacsa, OpenOffice'a, Firefoxa -- wszystko to skonfigurowane, gotowe do użytku i nieużywane. Bo na Linuxie jest o wiele fajniej!

Fedora 8, aktualne wydanie jednej z największych dystrybucji Linuxa, w praktyce to to samo co komercyjny Red Hat (tzn. w mojej praktyce przy moich potrzebach). Korzystam z zaskakująco niewielu programów:
- Emacs - wielofunkcyjny, programowalny edytor. Nadaje się szczególnie dobrze do Haskella, C, C++, Pythona, Prologa, ...
- darcs - system kontroli wersji, bardzo przyjemny
- rdiff-backup - wymagający minimalnej konfiguracji, lecz potężny program do backupów
- xmonad - fenomenalny menadżer okien, bardzo wygodny
- Total Commander - tak, pod wine'm chodzi :-)
- foobar2000 - wolę go od mplayer'a czy xmms'a. Też chodzi pod wine'm.
- Swiftfox, czyli zoptymalizowany pod konkretną architekturę Firefox
- workrave - program ograniczający korzystanie z klawiatury i przypominający o odpoczynkach
- narzędzia wbudowane w Linuxa, o instalację których nie muszę się troszczyć

O xmonad i Emacsie wypada napisać osobne posty - już wkrótce!

Tymczasem wracam do chorowania. Dopadła mnie jakaś grypa...

Spełnialność formuł logicznych

Ogólnie rzecz biorąc problem spełnialności formuł logicznych jest bardzo złożony pod względem obliczeniowym. Przy tym naiwny algorytm który rozwiązuje ten problem jest bardzo prosty. W Prologu można go zapisać tak:

:- op(900,fy,neg).
:- op(1000,yfx,and).
:- op(1010,yfx,or).

sat( X ) :- eval( X, true ),!.

eval( true, true ) :- !.
eval( false, false ).

eval( neg X, true ) :- eval( X, false ).
eval( neg X, false ) :- eval( X, true ), !.
eval( X or Y, true ) :- ( eval( X, true ) ; eval( Y, true ) ), !.
eval( X and Y, true ) :- eval( X, true ), eval( Y, true ), !.

eval( X, false ) :- eval( X, true ), !, fail.
eval( _, false ).


Korzystamy tutaj mocno z wbudowanego w Prolog backtrackingu. Możemy sprawdzać spełnialność formuł w stylu
X or Y and neg (Y and X)

Przykładowa sesja:
?- [sat].
% sat compiled 0.00 sec, 2,936 bytes

Yes
?- sat( neg neg neg X ).

X = false ;;

No
?- sat( neg neg neg X and neg Y).

X = false,
Y = false ;;

No
?- sat( neg neg neg X and neg Y and Z).

X = false,
Y = false,
Z = true ;;

No
?- sat( X and neg X ).

No
?- sat( _ and neg _ ).

Yes
?-
% halt


Jak widać Prolog bywa zręcznym narzędziem do wyrażania pewnych algorytmów.

czwartek, kwietnia 10, 2008

Prolog nie jest taki zły

W poniedziałek stoczyłem prawdziwą batalię, usiłując stworzyć poprawnie działające predykaty wyższego rzędu rekurencyjnie przechodzące po termie aż zajdzie jakiś warunek. Byłoby więc to coś w rodzaju metamorfizmu. Moje wysiłki spełzły jednak na niczym, skończyłem wieczorem z funkcją:

mapRec( Pred, Arg, OutArg ) :-
   % tworzymy nowy predykat pomocniczy
   gensym( partial, UNIQ ),
    Left =.. [ UNIQ, X, Y ],
    PartialMapRec = ( Left :- mapRec( Pred, X, Y ) ),
    asserta( PartialMapRec, Ref ),
    % rozkładamy term wejściowy
    Arg =.. [ Comp, Op | Args ],
    map( UNIQ, Args, Args2 ), % dla termów atomowych mamy podstawę indukcji: map    nic nie robi na pustej liście
    % składamy term ponownie
    Arg2 =.. [ Comp, Op | Args2 ],
    % aplikujemy Pred i zwracamy wynik
    F =.. [ Pred, Arg2, OutArg ],
    F,
    erase( Ref ), % kasujemy predykat pomocniczy
    !.

mapRec( _, Arg, Arg ) :-
    write( final ), write(' '), write( Arg ), nl,
    final( Arg ), !.

Najogólniej rzecz biorąc, nie działała ona tak jak powinna. To znaczy coś tam niby liczyła, ale źle, a co gorsze była okropnie długa (jak widać) i beznadziejna do debugowania. Ponadto wymagała ingerencji w bazę predykatów Prologa, co jest moim zdaniem brzydkie i psuje trochę wydajność. Zakończyłem nieudaną sesję programistyczną mocno zniesmaczony Prologiem, a konkretnie jego odpornością na próby metaprogramowania.

Dzisiaj jednak dokopałem się do paru predykatów, które znacząco uprościły konstrukcję mapRec'a:
mapRec( Pred, Arg, OutArg ) :-
    Arg =.. [ Comp, Op | Args ],
    maplist( mapRec( Pred ), Args, Args2 ),
    Arg2 =.. [ Comp, Op | Args2 ],
    apply( Pred, Arg2, OutArg ),
    !.

mapRec( _, Arg, Arg ) :-
    write( final ), write(' '), write( Arg ), nl,
    final( Arg ), !.

Nie taki Prolog straszny jak go malują :-)

Bazując na poznanej wiedzy napisałem wreszcie simp'a, czyli predykat upraszczający wyrażenia arytmetyczne. Jest to jedno z zadań na pracownię z Programowania w Logice. Wyszedł niedługi i raczej nie ma zbyt wielu błędów :-) Leży tutaj.

Do uruchomienia potrzebny jest interpreter Prologa. Ja używam SWI-Prologa, ale program powinien działać na każdym sensownym interpreterze.

Dla tych którzy nie mieli nigdy doczynienia z Prologiem, oto jak wygląda przykładowa sesja:

?- [simp].
% simp compiled 0.00 sec, 11,264 bytes

Yes
?- simp( x-x, EXP ).

EXP = 0 ;;

No
?- simp( x-x*1*2*3, EXP ).

EXP = x* -5 ;;

No
?- simp( 1*2*(1+1+1)*x-x*1*2*3, EXP ).

EXP = 0 ;;

No
?- simp( x*(y+z*(1+p)-p*(x-z)), EXP ).

EXP = - (p* (x*x))+ (p* (x*z)*2+ (x*y+x*z)) ;;

No
?-
% halt


Choć Prolog bywa wkurzający, to jednak przyjemnie jest nauczyć się czegoś nowego.

poniedziałek, kwietnia 07, 2008

Budzik, revisited

Kiedyś pisałem budziki w C (tu, tu), albo z wykorzystaniem wtyczek do foobar'a. Ten czas już minął! Nigdy więcej niskopoziomowego C do takich zadań.
W międzyczasie miałem program napisany w Pythonie, ale gdzieś mi się zapodział. Nie potrzebuję go już więcej bo oto jest - Budzik 3000! Innowacyjne rozwiązanie, nie wymaga żadnej konfiguracji i jest w pełni kompatybilne z czymkolwiek posiadającym konsolę, program sleep i mplayer (np. mój Linux).
sleep 7h 30m && mplayer ~/budzik/*.mp3

Szybkie, dokładne i wygodne. Zostawić w konsoli i iść spać...

Minus jeden, czyli co aktualnie porabiam

Studiuję aktualnie w -1, zwanym też ii, II, albo Instytutem Informatyki Uniwersytetu Wrocławskiego. Fajnie nie?

Ano fajnie, bo wydział należy do tych wybitnie fajnych. Ficzery:
- za wyjątkiem 8 obowiązkowych przedmiotów (po 2 przez pierwsze 4 semestry) program studiów jest układany samodzielnie przez studenta. Chcę się zapisać na przedmiot XYZ? Robię to. Chcę się z niego wypisać? Przez pierwsze dwa tygodnie semestru można to robić bez konsekwencji.
- wysoki poziom. Serio, nie żartuję - jest się tu czego nauczyć i daje to ogromną satysfakcję. Później także dobry start na rynku pracy, ale to już szczegół :-) Kilku absolwentów pracuje nawet dla Google czy M$, inni założyli jakiś portal społecznościowy
- atmosfera, nie tylko dzięki klimatyzacji w budynku. Wśród studentów panuje spora różnorodność osobowości i zawsze znajdzie się ktoś, z kim można porozmawiać.
- mniej ważne, ale jednak: fajny, nowy budynek położony dogodnie przy Moście Grunwaldzkim.

Szczęśliwie zaliczyłem pierwszy rok i jestem aktualnie na 4-tym semestrze. Nie mam nawet tak u nas popularnej poprawki z Programowania :-) Choć być może uda mi się coś wymyślić z JFiZo (Języki Formalne i ZŁOżoność Obliczeniowa), którego rokrocznie zdaje jakieś 30% osób. To raczej mało, szczególnie że wiele z tych, którym się udaje, robi ten przedmiot nie po raz pierwszy. AiSD (Algorytmy i Struktury Danych) też są obiecujące w tym względzie.

Przedmioty na których najlepiej się bawiłem? Będzie jakoś tak:
1. Programowanie (Haskell rządzi!)
2. Programowanie funkcjonalne (niefajny był tam OCaml, ale poznałem podstawy tego paradygmatu)
3. Przetważanie Języka Naturalnego (Natural Language Processing, NLP) (program do pisanie wierszy)
4. Kurs C (grupa zaawansowana: graficzna gra w Sudoku, gra 3D, kalkulator+wykresy 3D, znajdowanie trasy przejazdu komunikacją miejską we Wrocławiu)

Na razie to tyle, stay tuned.

Podpalmy żyrafę na nowo!

Czyli reaktywacja bloga :-)

Nosiłem się z tym zamiarem od jakiegoś czasu, z kilku powodów:
- w moim życiu dzieje się sporo rzeczy a pamięć mam krótką;
- fajnie podzielić się tym, co właśnie się odkryło, dowiedziało itp.
- lubię pisać, a ostatnio nie miałem do tego wielu okazji
- były inne powody, ale aktualnie ich nie pamiętam :-)

Plan na najbliższe kilka postów:
1. Opisać co aktualnie robię
2. Najnowsze zmiany w sprzęcie - kupno laptopa
3. Moja konfiguracja tegoż sprzętu
4. Coś o xmonad
5. Coś o workrave
6. Wpis o zespole/projekcie Ayreon

Co potem? Ha! Któż to może wiedzieć?

Na razie nie zmieniam wyglądu bloga, zaktualizowałem tylko linki. Szczególnie polecam xkcd!

Edit: eksperymentalnie włączam reklamy. Gdyby komuś przeszkadzały, to są odpowiednie wtyczki do Firefoxa...

środa, maja 24, 2006

Wakacje...

Tak, tak, wakacje już się rozpoczęły! Przynajmniej dla mnie...

Będzie to dla mnie, wbrew pozorom, bardzo zajęty okres, być może w większym nawet stopniu niż w trakcie roku szkolnego. Równocześnie jednak nieporównywalnie od niego przyjemniejszy.

Moje plany? Wizje? Pomysły? Mam ich kilka. Mimo wszystko, po części jadę autobusem... linii "Rnd":



Szerokiej drogi mat-fizy i nie tylko!

sobota, maja 13, 2006

Mat(ura/ematyka)

Ach tak, matura z matmy. Jakoś poszło - aczkolwiek nie obyło się bez błędów. Poziom podstawowy był prosty, rozszerzony wymagał znacznie więcej energii. Swoją drogą - jestem za obowiązkową maturą z matematyki. Chociażby po to, aby obyło się bez takich przypadków liczenia 30%... ;-)
Promocja w Tesco...
Tymczasem zaś - fizyka i ustny polski.

poniedziałek, maja 08, 2006

Oral

Uczyli ustna - matura z angielskiego. Przyszedłem dzisiaj na 13:00, o 15:00 byłem w domu... w sumie jedna z najprzyjemniejszych części jak sądzę :) Dostałem 20 na 20 punktów, czyli 100%... :D

czwartek, maja 04, 2006

Ciepełko

Ach, matura, matura, matura, matura, matura...
Czekanie, arkusz, bułka, grzesiek w czekoladzie, telefon, bułka, czekanie, arkusz...
Cóż mam powiedzieć? Zmęczyłem się tym polskim i tyle.

Dwa razy pierwszy temat. Za jakiś czas pójdę oglądać film (po angielsku, ja jutro).

Pozytywne jest to, że nie mam negatywnych uwag ;-)


Swoją drogą to jakaś ciekawa zależność: na zewnątrz ciepełko i na maturze ciepełko.

wtorek, kwietnia 25, 2006

Szkoła medialna

Zrobione dziś w szkole zdjęcia można obejrzeć w galerii na stronie:
http://www.matfiz2lo.yoyo.pl/

Sukcesywnie będę tam dodawać różne fotografie. Filmiki będę z kolei dodawał do serwisu video.google.com. Link do znalezienia na powyższej stronie.

Edycja:

Zapomniałem dodać, że oryginały w znacznie lepszej rozdzielczości mogę przegrać na dowolną płytkę - zajmują ok 185 Mb.

Edycja(2):

Warto obejrzeć także stronkę z filmikami Śliska. "Nawiedzona Szatnia","Intro","Oż!"...
www.dropshots.com/matfiz

poniedziałek, kwietnia 24, 2006

Już tylko miesiąc

I to bynajmniej nie do matury, lecz do jej końca. Trzeba się zacząć uczyć ;-)

Nie mam czasu pisać na blogu. Może od czasu do czasu napisze coś krótkiego aby się oderwać, ale nie liczcie na jakiekolwiek dłuższe formy.

Za wypełniacz niech posłużą zdjęcia.








Do zobaczenia po maturze :-)

wtorek, kwietnia 18, 2006

Życie

Ach życie...

"Rozprężę szeroko ramiona,
Nabiorę w płuca porannego wiewu,
W ziemię się skłonię błękitnemu niebu
I krzyknę, radośnie krzyknę :
- Jakie to szczęście, że krew jest czerwona !"


Ślizgając się po powierzchni realizmu dochodzę do wniosku, że najsilniej odczuwa się wiosnę gdy po sześciu zegarowych godzinach matematyki wychodzi się z kochaną osobą w słoneczny dzień. Lub też gdy spaceruje się po Parku Południowym i nie jest się sam.

Wczoraj wyjechałem do Wrocławia, wróciłem dziś mniej więcej w okolicach 20:30. Przy okazji stwierdziłem po raz kolejny, że pociągi relacji Wrocław-Wałbrzych oddziałują z cząstkami v indukując pole nasenne. Musiałem uważać żeby nie przegapić stacji...

Tymczasem przymierzam się do lekkich zmian w wyglądzie strony. Czas pokaże co się z tego ziści. Z nieoficjalnych źródeł mogę przekazać, że pojawi się tu i ówdzie cząstka v i może jakaś Żyrafa Rasa (v).

Tak, wiem, że smucicie się że już kończe ten post...


"Pa!", jako rzecze Lama.


Ach, byłbym zapomniał. Nowy Vault!

niedziela, kwietnia 16, 2006

Budzik

Dziś trochę inaczej.

#include "time.h"
#include "stdlib.h"

int main()
{
int sec = 1;
int min = 60*sec;
int h = 60*min;
sleep(4*h+30*min);
system("./foobar2000.exe budzenie.fpl");
return 0;
}


Może się okazać trochę niedokładny ale trudno się mówi.