Die Aufgabe ist, die Zeit zwischen zwei Mausclicks zu messen (und über die gemessenen Zeiten zu mitteln).
bpm :: Component bpm = do first_click <- mkBVar 0.0 last_click <- mkBVar 0.0 clicks_so_far <- mkBVar 0.0 average_bpm <- mkBVar 0.0 ...
let infos = mkLabel ... let reset = mkButton [ text "reset" ] ... let click = mkButton [ text "click" ] ... ... nbeside [ reset, infos, click ] ... let men = mkMenu [] [ mcascade [text "file"] $ mkMenu [] [mbutton [text "quit"] quit] ] withRootWindow [title "bpm counter", useMenu men] bpmSo sagen wir "das Label soll immer den aktuellen Wert der Variablen anzeigen":
let infos = mkLabel [ font (Font "10x20") , textB $ lift1 ( \ x -> "average bpm : " ++ show x ) $ bvarBehavior average_bpm ]Jedesmal, wenn average_bpm geschrieben wird, zeichen FranTk das Label neu.
Die einfachste Art von Listener ist ein input-Listener für eine BVar. Bei "reset" schicken wir z. B. eine 0.0 an den input-Listener von clicks_so_far. Wo kommt die 0.0 her? Vom Button bekommen wir ein Ereignis vom Typ Event (), wir brauchen aber Event Double. Wir müssen das mappen:
mapL ( \ () -> 0.0 ) ( input clicks_so_far )Zur Übung: welchen Typ hat mapL? Offenbar
mapL :: (a -> b) -> (Listener b -> Listener a)Das ist ein kontravarianter Funktor.
Im eben gezeigten Fall ist uns ja der Wert des ankommenden Ereignisses egal. Wichtig ist, daß wir daraus eine 0.0 machen. Dafür gibt es
tellL :: Listener a -> a -> Listener b tellL l x = mapL (const x) lNun wollen wir nicht nur eine Variable updaten, sondern mehrere. Dazu müssen wir Listener parallelschalten. Das geht mit
mergeL :: Listener a -> Listener a -> Listener a anyL :: [ Listener a ] -> Listener aTatsächlich ist die Definition (siehe FranTk/src/FranSrc/Listener1.hs)
anyL = foldr mergeL neverLWas neverL macht, können wir uns denken: nichts.
snapshotL :: Behavior a -> Listener (b,a) -> Listener bWir benutzen das so:
timed :: Listener Time -> Listener () timed listener = snapshotL time $ mapL ( \ ((), t) -> t ) $ listenerBeachte wieder die Umkehrung der Typen.
let reset = mkButton [ text "reset" ] ( timed $ anyL [ input first_click , input last_click , tellL (input clicks_so_far) 0 , tellL (input average_bpm ) 0 ] )sowie die eigentliche Rechnung hier
let click = mkButton [ text "click" ] ( timed $ anyL [ input last_click , tellL (bvarUpdInput clicks_so_far) (1 +) , snapshotL ( bvarBehavior first_click ) $ snapshotL ( bvarBehavior clicks_so_far ) $ mapL ( \ ( (t, f), c ) -> (c + 1) * 60 / (t - f) ) $ input average_bpm ] )Hier ist der vollständige Quelltext.
Hausaufgabe: Zeigen die bmp-Zahl zusätzlich durch einen variabel langen Balken oder so etwas ähnliches an.