Writing a generic event handler

Hi Haskell Cafe, I'm interested in writing some events and event handlers in Haskell. I already have a Loop data structure, and I intend to use it for this purpose: -- Create event tEvent <- newLoop (return ()) -- Register event handlers tHandler1 <- newLoop (putStrLn "Handler1") tHandler2 <- newLoop (putStrLn "Handler2") splice tEvent tHandler1 splice tEvent tHandler2 -- Fire event action <- doLoop tEvent action doLoop :: Monad m => TVar (Loop (m ())) -> STM (m ()) doLoop tLoop = do aLoop <- readAsList tLoop return $ sequence_ aLoop My question is: Is it possible to write a generic doLoop that works over arbitrary functions? For instance the following code wouldn't work because the event provides one argument and the handler takes one argument: -- Create event tEvent <- newLoop (\x -> return ()) -- Register event handlers tHandler1 <- newLoop (\x -> putStrLn ("Handler1" ++ show x)) tHandler2 <- newLoop (\x -> putStrLn ("Handler2" ++ show x)) splice tEvent tHandler1 splice tEvent tHandler2 -- Fire event action <- doLoop tEvent action 123 Thanks, -John Full source code for Loop type: module Fx.STM.Loop where import Control.Monad import Fx.STM.Util import GHC.Conc import System.IO.Unsafe -- Transactional loop. A loop is a circular link list. data Loop a = ItemLink { item :: a , prev :: TVar (Loop a) , next :: TVar (Loop a) } -- Create a new empty transactional loop. newLoop :: a -> STM (TVar (Loop a)) newLoop item = do tLoop <- newTVar undefined writeTVar tLoop (ItemLink item tLoop tLoop) return tLoop -- Splice two transactional loops. This will join two loops if they were -- originally separate, or split a single loop if the links were originally -- part of the same loop. No change occurs if the two links are identical. splice :: TVar (Loop a) -> TVar (Loop a) -> STM () splice tLink0 tLink1 = do aLink0 <- readTVar tLink0 aLink1 <- readTVar tLink1 let tLink0Prev = prev aLink0 let tLink1Prev = prev aLink1 writeTVar tLink0 aLink0 { prev = tLink1Prev } writeTVar tLink1 aLink1 { prev = tLink0Prev } aLink0Prev <- readTVar tLink0Prev aLink1Prev <- readTVar tLink1Prev writeTVar tLink0Prev aLink0Prev { next = tLink1 } writeTVar tLink1Prev aLink1Prev { next = tLink0 } return () -- Unlink a single link from a transactional loop. unlink :: TVar (Loop a) -> STM () unlink tLink = do (ItemLink item tLinkPrev tLinkNext) <- readTVar tLink aLinkPrev <- readTVar tLinkPrev writeTVar tLinkPrev aLinkPrev { next = tLinkNext } aLinkNext <- readTVar tLinkNext writeTVar tLinkNext aLinkNext { prev = tLinkPrev } writeTVar tLink (ItemLink item tLink tLink) return () -- Read the length of the loop. readLength :: TVar (Loop a) -> STM Int readLength tLink = do list <- readAsList tLink return $ length list readLinks :: TVar (Loop a) -> STM [TVar (Loop a)] readLinks tLink = readLinksUntil tLink tLink readLinksUntil :: TVar (Loop a) -> TVar (Loop a) -> STM [TVar (Loop a)] readLinksUntil tLink tLinkEnd = do (ItemLink _ tLinkPrev tLinkNext) <- readTVar tLink return [] if tLinkNext == tLinkEnd then return [tLink] else do tail <- readLinksUntil tLinkNext tLinkEnd return $ tLink:tail -- Read the elements of the loop as a list starting from tLink. readAsList :: TVar (Loop a) -> STM [a] readAsList tLink = readAsListUntil tLink tLink -- Read the elements of the loop as a list starting from tLink -- and terminating non-inclusively at tLinkEnd. readAsListUntil :: TVar (Loop a) -> TVar (Loop a) -> STM [a] readAsListUntil tLink tLinkEnd = do (ItemLink item tLinkPrev tLinkNext) <- readTVar tLink if tLinkNext == tLinkEnd then return [item] else do tail <- readAsListUntil tLinkNext tLinkEnd return $ item:tail -- Create a new loop from a list. newFromList :: [a] -> STM (TVar (Loop a)) newFromList [item] = newLoop item newFromList (item:items) = do tLink <- newLoop item tLinkRest <- newFromList items splice tLink tLinkRest return tLink doLoop :: Monad m => TVar (Loop (m ())) -> STM (m ()) doLoop tLoop = do aLoop <- readAsList tLoop return $ sequence_ aLoop

John Ky
My question is: Is it possible to write a generic doLoop that works over arbitrary functions?
Yes and no, that is, you can overcome the no. The following code typechecks, and would run nicely if there was a fixed version of reactive, by now[1]. Event handlers can take one arbitrary argument and return anything (as long as it's the same as other handlers), and may be curried before registration, of course. As you can stuff anything you please into one argument, I doubt you'll hit a wall there. Note the usage of Maybe to dispatch, you should be able to do something similar with a plain Maybe monad, without all that Event stuff. Either might also be a good idea. [1] It would also feature splices that can be spliced further and other things quite similar to pattern-matching. TBH, I got tired of not running the code and intimidated by fixpoints of Event streams. module Main where -- current darcs XHB -- hack it to export Graphics.XHB.Connection.Types -- get rid of the Show constraint in Reactive's filterE (or was it justE?) import Graphics.XHB as X import FRP.Reactive as R import FRP.Reactive.LegacyAdapters import Control.Concurrent import Control.Monad import Control.Applicative import Data.Maybe import Data.Monoid createSimpleWindow :: Connection -> Int -> Int -> Int -> Int -> IO WINDOW createSimpleWindow c x y w h = let s = getScreen c black = black_pixel_SCREEN s root = root_SCREEN s depth = root_depth_SCREEN s visual = root_visual_SCREEN s in do id <- newResource c createWindow c (MkCreateWindow depth id root (fromIntegral x) (fromIntegral y) (fromIntegral w) (fromIntegral h) 0 0 visual (toValueParam [(CWBackPixel,black) ,(CWEventMask,toMask [ EventMaskExposure , EventMaskKeyPress --, EventMaskFocusChange ])])) return id createSimpleGC :: Connection -> IO GCONTEXT createSimpleGC c = let s = getScreen c root = root_SCREEN s white = white_pixel_SCREEN s in do g <- newResource c createGC c (MkCreateGC g (castXid root) (toValueParam [(GCForeground,white) ,(GCGraphicsExposures,0) ])) return g printErrors c = (forkIO . forever) $ waitForError c >>= print getScreen = head . roots_Setup . conf_setup . conn_conf castXid = fromXid . toXid main = do (Just c) <- connect printErrors c print $ displayInfo c gc <- createSimpleGC c w <- createSimpleWindow c 0 0 640 480 lock <- newEmptyMVar let quit = putMVar lock () mapWindow c w sync <- makeSync evs <- eventsX sync c forkIO $ adaptE $ braidE (const $ putStrLn "Unhandled Event") [ xEventSplice expose , xEventSplice (keyPress quit) ] evs readMVar lock type Splice a b = (R.Event a, R.Event b) -> (R.Event a, R.Event b) xEventSplice :: X.Event c => (c -> b) -> Splice SomeEvent b xEventSplice = mkSplice fromEvent mkSplice :: (a -> Maybe c) -> (c -> b) -> Splice a b mkSplice f g (a,b) = ( filterE (isNothing . f) a , (fmap g . justE . fmap f) a `mappend` b ) braidE :: (a -> b) -> [Splice a b] -> R.Event a -> R.Event b braidE f ss i = b `mappend` fmap f a where (a, b) = (foldr (.) id ss) (i, mzero) expose :: Expose -> Action expose = const $ putStrLn "expose" keyPress :: Action -> KeyPress -> Action keyPress quit = const $ putStrLn "keyPress" >> quit type Sync = Clock TimeT makeSync = makeClock eventsX cl c = do (sink, res) <- makeEvent cl (forkIO . forever) $ {-putStrLn "tick " >>-} waitForEvent c >>= sink return res -- (c) this sig last receiving data processing entity. Inspect headers for copyright history. All rights reserved. Copying, hiring, renting, performance and/or quoting of this signature prohibited.
participants (2)
-
Achim Schneider
-
John Ky