Bit na dziś

23 kwietnia 2012, 19:08:13

Work

22 marca 2012, 08:30:56

Jumpy

09 marca 2012, 22:23:57

Nadal mam mieszane uczucia co do Functional Reactive Programming, ale fakt, że animacje robi się całkiem przyjemnie. Po trzech wieczorach walk, skacząca piłeczka zaczęła skakać jak trzeba.

Deklaratywny styl pisania się przyjemnie wyjaśnia, bo wystarczy przetłumaczyć na polski to, co jest zapisane w kodzie. Nie będę mówił, co ma robić kod, bo to widać na filmie.

count :: Event a -> Behavior Int
count ev = 0 `stepAccum` ev ->> (+1)

Pomocnicza funkcja count zwraca zachowanie, które zlicza wystąpienia danego zdarzenia. Używam jej do debugowania. Kod powyżej czytany dosłownie mówi, że dla zdarzenia ev count zwraca zachowanie, które początkowo ma wartość 0, a następnie przy każdym wystąpieniu zdarzenia ev inkrementuje swoją wartość.

A teraz opiszemy scenę. Przyspieszenie w osi Y wynosi -5. Zachowanie falling to wartość całki po czasie z funkcji przyspieszenia. Innymi słowy prędkość w spadku swobodnym.

yacc = -5
falling = integral yacc

Najciekawszy fragment kodu to właśnie definicja prędkości piłeczki. Zaczynamy od zachowania równego falling, czyli spadku swobodnego. Dodatkowo, prędkość ma reagować (wielokrotnie) na dwa zdarzenia. Pierwsze to bounce, które będzie występować przy zderzeniu z platformą. W odpowiedz chcemy odbić piłeczkę, tracąc część prędkości... chyba, że piłka ma już bardzo małą prędkość i w tej sytuacji po prostu ją unieruchamiam.

Obliczenia dla czytelności umieściłem w funkcji doBounce. Kombinator snapshot_ pozwala pobrać wartość zachowania yvel w momencie, kiedy wystąpi zdarzenie. Ta wartość jest argumentem funkcji doBounce, która zwraca nowe zachowanie. Zwracam uwagę na użycie falling, aby piłka po odbiciu nadal spadała.

Drugie zdarzenie, na które reaguje prędkość, to naciśnięcie dowolnego klawisza. W takim wypadku resetuję prędkość na wartość 4 i pozwalam piłeczce spadać. Efekt jest taki, jakbyśmy uderzyli piłeczkę od dołu.

Dla jasności, magiczny operator

.|.
to alternatywa zdarzeń.

yvel = falling `switch`
        (  bounce `snapshot_` yvel =>> doBounce
       .|. key ->> constB 4 + falling
        )
 
doBounce v | v > -0.5  = constB 0
           | otherwise = constB (0.8 * negate v) + falling

Idąc dalej z tą fizyką, położenie w osi Y jest całką prędkości, a w osi X niech będzie stałe.

ypos = integral yvel
xpos = 0

Odbicie następuje, kiedy prędkość skierowana jest w dół, a położenie piłeczki osiągnie magiczną wartość -1.7, bo tam się zaczyna platforma.

bounce  = when (ypos <=* -1.7 &&* yvel <* 0)

Żebym wiedział co się dzieje w programie, będę sobie wyświetlał liczniki tych dwóch zdarzeń:

bounces = count bounce
keys    = count key

Na koniec ze wszystkich cząstkowych zachowań składam jedno duże. Zachowanie, którego wartością jest rysunek wyświetlany na ekranie.

paint red (translate (0, -2.15) (rec 2 0.3))
`over` paint yellow (translate (xpos, ypos) (ell 0.3 0.3))
`over` text (lift1 (printf "Ypos: %+.4f") ypos)    20 20
`over` text (lift1 (printf "Yvel: %+.4f") yvel)    20 40
`over` text (lift1 (printf "bounces: %i") bounces) 20 60
`over` text (lift1 (printf "clicks:  %i") keys)    20 80

Cały kod tutaj.

GitHub fail

07 marca 2012, 22:46:38

We take security seriously and recognize this never should have happened. Tak GitHub pisze w mailu rozesłanym do wszystkich użytkowników. A kiedy zgłaszano im problem, zupełnie to olali. Ekipa od railsów zresztą też. Fail. Przynajmniej takie czytałem plotki.

Paddleball

28 lutego 2012, 22:46:30

Zamiast GUI dla wież Hanoi po prostu machnąć w Gtk, zainteresowałem się FRP. Matematyczna teoria dawała mi słabe wyobrażenie o co chodzi. Ogólny feel można sobie wyrobić czytając tutorial do Flapjaxa, a przynajmniej oglądając przykłady. W końcu trafiłem na Haskell School of Expression Paula Hudaka i dochodząc od zera do działającego programu zacząłem nabierać rozeznania jak to działa, chociaż na razie trudno mi wyobrazić sobie jakby wyglądał jakiś program z klikanym GUI.

FPR polega na tym, żeby bardziej deklaratywnie podejść do opisu interakcji zachodzących w programie. Zamiast asynchronicznych wystąpień zdarzeń, w odpowiedzi na które uruchamiany jest jakiś callback, modelujemy aplikację jako funkcję czasu i nieskończonej listy zdarzeń. Prawda, że brzmi bardzo przekonująco? ;-) Tu jest przykład, na którym trochę widać na czym cała ta deklaratywność ma polegać.

Robimy grę. Piłeczka odbijana paletką sterowaną myszką. Trzy ściany ograniczające jej ruch od prawej, lewej i z góry. No to deklarujemy. Ściany to niebieskie prostokąty. Paletka to czerwony prostokąt, którego jedna współrzędna jest stała, a druga równa współrzędnym kursora. A piłka to piłka. Prędkość zmienia się na przeciwną po zderzeniu ze ścianą. Zderzenie ze ścianą jest wtedy, kiedy piłka przekroczy określone współrzędne. A położenie jest całką prędkości (sic!) Ot, pestka. Kod:

paddleball vel = walls `over` paddle `over` pball vel
 
walls = let upper = paint blue (translate (0, 1.7) (rec 4.4 0.05))
            left  = paint blue (translate (-2.2, 0) (rec 0.05 3.4))
            right = paint blue (translate ( 2.2, 0) (rec 0.05 3.4))
        in upper `over` left `over` right
 
paddle = paint red (translate (fst mouse, -1.7) (rec 0.5 0.05))
 
pball vel =
    let xvel    = vel `stepAccum` xbounce ->> negate
        xpos    = integral xvel
        xbounce = when (xpos >* 2 ||* xpos <* -2)
        yvel    = vel `stepAccum` ybounce ->> negate
        ypos    = integral yvel
        ybounce = when (ypos >* 1.5
                  ||* ypos `between` (-2.0, -1.5) &&*
                      fst mouse `between` (xpos - 0.25, xpos + 0.25))
    in paint yellow (translate (xpos, ypos) (ell 0.2 0.2))
 
x `between` (a, b) = x >* a &&* x <* b

Mój mózg ciągle się nie pogodził z tym jak wysoki jest tu poziom abstrakcji. Procesor niestety też nie. Nie wiem, jak to działało uruchamiane przez autora w 2000 roku. Dziś na moim skromnym sprzęcie troszkę rwie, co widać na filmiku.

DXR semantic code browsing

25 lutego 2012, 13:40:24

Programiści

22 lutego 2012, 21:49:38

Czołówka programistów C++ mówi z dziwnym akcentem i chodzi w adidasach. Link do materiałów dowodowych: Going Native 2012.

Ostatnio zmieniam zdanie o Microsofcie. Poza tym, że kradną od wszystkich pomysły, to jednak dokładają też coś wartościowego od siebie. Cały ten Channel 9 i research związany z Haskellem są tego dobrym przykładem. No i pracownicy mają fajne biura... Z oknami. Heh.

Twierdza

22 lutego 2012, 21:35:33

Po czym podsumował to tak:
— Tchórzem i zdrajcą nazywam każdego, kto się skarży na cudze winy albo na potęgę nieprzyjaciela.
Ale nadal nikt go nie rozumiał.
— Istnieją przecież pewne oczywiste fakty, za które nie jesteśmy odpowiedzialni...
— Nie ma takich — odparł ojciec.

Antoine de Saint-Exupéry

Mimo wielu wad, jest to interesująca lektura.

Wieże Hanoi III

18 lutego 2012, 23:25:20

Pora zacząć robić coś ciekawego z tymi biednymi wieżami. Dzisiaj na warsztat biorę moje paskudne wskaźniki, które z braku lepszego pomysłu nadal tak nazywam. Ostatnia wersja kodu wyglądała w ten sposób:

ptrA = Pointer
     { name = A
     , pop  =       modset $ \(Towers (d:as) bs cs) -> ( d, Towers    as  bs cs)
     , push = \d -> modset $ \(Towers as bs cs)     -> ((), Towers (d:as) bs cs)
     }

Pozostałe dwa na wieże B i C łatwo sobie wyobrazić. Nie podoba mi się w nich przede wszystkim to, że zawierają jednocześnie wskazanie na wieżę, jak i implementację popa i pusha. Wpadłem tutaj w pułapkę swojego pierwszego rozwiązania i nie przychodziło mi do głowy nic ciekawego, więc zacząłem googlać za czymś w stylu "haskell member pointer" i oto odkryłem, tada, soczewki. Nie mam pojęcia czy to poprawne tłumaczenie.

Moment, w którym mnie uderzyła ta prosta myśl getter i setter, został zwieńczony stosownym face palmem. Prezentacja What are lenses? wyraźnie sugeruje, że soczewki to coś ponad prostą koncepcję gettera i settera, ale mój prosty umysł na razie rozumie tylko tyle. Studia były kompletną stratą czasu, skoro teorię kategorii nadal muszę traktować jak religię.

Implementacja jest banalna. We wskaźniku pozostawiam tylko funkcje get i set, a pop i push wyciągam na zewnątrz.

data Pointer = Pointer
             { name     :: TowerName
             , getTower :: Towers -> Tower
             , setTower :: Towers -> Tower -> Towers
             }
 
ptrA = Pointer
     { name = A
     , getTower = towerA
     , setTower = \t a -> t {towerA=a}
     }
 
-- ptrB i ptrC analogicznie
 
modset :: (Tower -> (a, Tower)) -> Pointer -> TowersState a
modset f ptr = do
    s <- get
    let (r, s') = f $ getTower ptr s
    put $ setTower ptr s s'
    return r
 
pop :: Pointer -> TowersState Disc
pop = modset (\(x:xs) -> (x, xs))
 
push :: Disc -> Pointer -> TowersState ()
push d = modset (\xs -> ((), (d:xs)))

Tradycyjnie kolejna warstwa abstrakcji spowodowała wzrost ilości linii. Całość kodu w tej wersji tutaj.

Wszystko ładnie, tylko ilość kodu wzrosła niebotycznie. Ale skoro już wiem, że jest coś takiego jak soczewki, dlaczego by ich nie użyć. Na hackage jest kilka implementacji, w tym takie, które używając Template Haskell prawie same nam te soczewki wygenerują. Wybrałem data-lens bo wyglądały najprościej.

Na marginesie wspomnę, że Template Haskell to moje kolejne odkrycie. Do składni trzeba się jak zwykle przyzwyczaić. Co mi się podoba, to że można generować kod pisząc "zwyczajny" kod w Haskellu. Nie są to jakieś śmierdzące makra C czy toporne i ułomne szablony C++. Zdaje się, że najlepiej byłoby to porównać do makr Lispa, chociaż mam wrażenie, że te Lispowe są fajniejsze... bo od składni lispa trudno wiele wymagać i też trudno ją w jakikolwiek sposób pogorszyć.

Wracając do tematu. Soczewkę robi się z gettera i settera:

lens :: (a -> b) -> (b -> a -> a) -> Lens a b

Przypominam, że typy deklarowane przy użyciu składni record mają gotowy getter, a dla settera istnieje syntactic sugar:

data Foo = Foo { bar :: Int }
get = bar
set b foo = foo { bar = b}

Konwencja nazewnicza w implementacjach soczewek jest taka, żeby zmienić nazwę pola w rekordzie dodając _, a soczewkę nazwać tak, jak nazywało się wcześniej pole. Czyli:

data Foo = Foo {bar_ :: Int }
bar = lens (bar_) (\b foo -> foo {bar_ = b}

Ten kawałek kodu powyżej trzeba powtórzyć dla wszystkich interesujących nas pól. Akurat użyta przeze mnie implementacja nie ma szablonu, który to wygeneruje. Moje wskaźniki musiały jednak zostać, ponieważ poza getterem i setterem przechowywały również nazwę wieży potrzebną do generowania ruchów. Znacznie się jednak uprościły i pozwoliłem sobie na bardziej zwięzły zapis:

type TowerLens = Lens Towers Tower
data Pointer = Pointer TowerName TowerLens
 
ptrA = Pointer A (lens towerA_ (\a s -> s { towerA_ = a}))
ptrB = Pointer B (lens towerB_ (\a s -> s { towerB_ = a}))
ptrC = Pointer C (lens towerC_ (\a s -> s { towerC_ = a}))

Łącząc soczewkę z odpowiednią funkcją otrzymujemy funkcję, która operuje na całym rekordzie, ale modyfikuje tylko wskazane pole. W zapisie wygląda to na tyle czytelnie, że wyrzuciłem również brzydką funkcję modset, która rozdzielała monadyczny kod od samego push i pop. Teraz operacje te są na tyle zwięzłe, że czytelniej było zapisać to tak:

pop :: TowerLens -> TowersState Disc
pop lense = do
    (x:xs) <- gets (getL lense)
    modify $ lense `setL` xs
    return x
 
push :: Disc -> TowerLens -> TowersState ()
push d lense = modify $ lense `modL` (\xs -> d:xs)

Nie przyszło mi do głowy, jak ładniej zrobić pop. Za to push jest moim zdaniem śliczny. Funkcja modify (z monady State) przyjmuje operację na stanie (reminder: stan to trzy wieże). My mamy operację na jednej z wież, ale dzięki (lense `modL`) zamieniamy ją w dokładnie to, czego potrzebuje modify.

Cały kod wkleiłem tutaj. Ostatecznie zostało mniej linii niż w pierwszej wersji. Trochę oszukiwałem, żeby to się udało, ale cel uświęca środki i ogłaszam sukces.

Teraz można się zastanowić co dalej. Dla treningu spróbuję użyć monady Writer do zapamiętywania sekwencji wykonywanych ruchów. A potem może graficzna animacja.

Wieże Hanoi II

13 lutego 2012, 19:32:51

Część pierwsza poprawiania poprzedniego kodu polega na użyciu monady State do przekazywania stanu wszystkich wież. Całość kodu dostępna tutaj, a ja wklejam tylko ciekawsze fragmenty. W gruncie rzeczy może to służyć za przykład użycia State, ale nic interesującego poniżej nie ma. Samą zabawę w poprawianie wskaźników zostawiłem sobie na koniec. :)

Zmienił się typ mojego "wskaźnika" tak, żeby funkcje push i pop działały w monadzie:

data Pointer = Pointer
             { name :: TowerName
             , pop  :: TowersState Disc
             , push :: Disc -> TowersState ()
             }

No i same wskaźniki zmieniły się tak:

ptrA = Pointer
     { name = A
     , pop  =       modset $ \(Towers (d:as) bs cs) -> ( d, Towers    as  bs cs)
     , push = \d -> modset $ \(Towers as bs cs)     -> ((), Towers (d:as) bs cs)
     }
 
modset :: (Towers -> (a, Towers)) -> TowersState a
modset f = do
    s <- get
    let (r, s') = f s
    put s'
    return r

Czuję w kościach, że tę operację na stanie można przeprowadzić bardziej elegancko, ale gdy to pisałem, nic czytelnego do głowy mi nie przyszło.

Właściwie powyżej widać same dodatkowe komplikacje. Na szczęście pozostała część interesującego kodu w wersji monadycznej wygląda zdecydowanie lepiej niż poprzednio. Tak czy inaczej, nowa warstwa abstrakcji, że się tak górnolotnie wyrażę, spowodowała wzrost ilości linii.

moveOne :: Pointer -> Pointer -> TowersState [Move]
moveOne src dst = do
    d <- pop src
    push dst d
    return [(name src, name dst)]
 
moveMany :: Int -> Pointer -> Pointer -> Pointer -> TowersState [Move]
moveMany 1 src dst aux = moveOne src dst
moveMany n src dst aux = do
    m1 <- moveMany (n-1) src aux dst
    m2 <- moveMany    1  src dst aux
    m3 <- moveMany (n-1) aux dst src
    return (m1++m2++m3)
 
solve :: Towers -> ([Move], Towers)
solve towers =
    let count = length $ towerA towers
    in runState (moveMany count ptrA ptrB ptrC) towers