tags:

views:

208

answers:

5

how can i do multiple calls to SDL.pollEvent :: IO Event until the output is SDL.NoEvent and collect all the results into a list?

in imperative terms something like this:

events = []
event = SDL.pollEvent;
while( event != SDL.NoEvent )
        events.add( event )
        event = SDL.pollEvent
+2  A: 

You can use monadic lists:

import Control.Monad.ListT (ListT)
import Control.Monad.Trans.Class (lift) -- transformers, not mtl
import Data.List.Class (takeWhile, repeat, toList)
import Prelude hiding (takeWhile, repeat)

getEvents :: IO [Event]
getEvents = 
    toList . takeWhile (/= NoEvent) $ do
        repeat ()
        lift pollEvent :: ListT IO Event

ListT from the "List" package on hackage.

yairchu
Why "repeat ()"?
Peaker
@peaker: `repeat () :: ListT IO ()` is an infinite IO-monadic list containing values that don't matter (`()`). then we `(>>)` it with `lift pollEvent` so that for each element of the infinite list we `pollEvent`. `takeWhile` makes it a finite monadic list and then `toList` makes it `:: IO [Event]`.
yairchu
That seems a bit weird.. Maybe makes more sense to use something like "repeatM (lift pollEvent)" ?
Peaker
@peaker: yeah, `repeatM pollEvent`. I added `repeatM` to the github tree and it will be there in the next hackage version
yairchu
+4  A: 

You could use something like:

takeWhileM :: (a -> Bool) -> IO a -> IO [a]
takeWhileM p act = do
  x <- act
  if p x
    then do
      xs <- takeWhileM p act
      return (x : xs)
    else
      return []

Instead of:

do
  xs <- takeWhileM p act
  return (x : xs)

you can also use:

liftM (x:) (takeWhileM p act) yielding:

takeWhileM :: (a -> Bool) -> IO a -> IO [a]
takeWhileM p act = do
  x <- act
  if p x
    then liftM (x:) (takeWhileM p act)
    else return []

Then you can use: takeWhileM (/=SDL.NoEvent) SDL.pollEvent

Peaker
I'd suggest `takeUntilM :: Monad m => (a -> Bool) -> m a -> m [a]` (with appropriate `return [x]` when `p x` is `false`) to avoid information loss (especially from IO monads). That may look normal when it just `SDL.NoEvent` but it might be wrong for `Left "system crash" :: Either String a`.
ony
Oh, and you probably want to build lazy list, so need to use `interleaveIO` from `System.Unsafe` (or something like that). I.e. something like `liftM (x:) (interleaveIO (unsafeTakeUntilM p act))`
ony
A lot of variants of `takeWhileM`: http://stackoverflow.com/questions/1133800/haskell-monadic-takewhile/1138153#1138153
KennyTM
`spanM` looks nice. So `curry (liftM fst . uncurry (flip (flip spanM . repeat)))` :)
ony
@peaker: imho using monadic lists is more modular
yairchu
@ony: Why lazy? This is polling events in the main loop of an SDL application. Logically, it's better to think of SDL pushing events to the program, not the program pulling events on demand. In fact, I think SDL will fall over if you don't clear the event queue quickly enough. Sometimes laziness doesn't make sense.
camccann
@ony: lazy-IO is bug prone. But if one would still insist on doing it, I don't think that creating a lazy `IO` version of `takeWhileM` is a nice and modular approach. Transforming to a lazy list should be done as a separate step from `takeWhile`. this could be achieved using monadic lists. (shameless plug for my own answer below)
yairchu
i actually want poll all events away from at the start of a gameloop iteration, so the IO stuff doesn't interfere with the functional game logic
@camccann: I don't think that leaving general memory overflow (because of list growing faster than consumers process it) resolving to the Haskell system is a good idea. I guess, SDL will have more specific way of handling event queue overflow (reducing frames, dropping interval timeouts, skipping mouse positions, beeping on key pressings etc). I agree that lazy list bug prone and it's better to do some kind of `mapSDLEvents_ :: (Event -> IO ()) -> IO ()` or `foldSDLEvents :: (a -> Event -> IO a) -> a -> IO a`. @yairchu: yes `spanM :: (a -> Bool) -> [m a] -> m ([a], [m a])` is good for that.
ony
A: 

i eventually stumbled over this code snippet in an actual SDL game from hackage

getEvents :: IO Event -> [Event] -> IO [Event]
getEvents pEvent es = do
  e <- pEvent
  let hasEvent = e /= NoEvent
  if hasEvent
   then getEvents pEvent (e:es)
   else return (reverse es)

thanks for your answers btw!

If that is so popular approach to pull out all events waiting in queue in one shot without processing it, than why SDL API doesn't provide it directly? That may help to avoid some sync. overhead for thread-safe queue.
ony
+1  A: 

Using these stubs for Event and pollEvent

data Event = NoEvent | SomeEvent
  deriving (Show,Eq)

instance Random Event where
  randomIO = randomRIO (0,1) >>= return . ([NoEvent,SomeEvent] !!)

pollEvent :: IO Event
pollEvent = randomIO

and a combinator, borrowed and adapted from an earlier answer, that stops evaluating the first time the predicate fails

spanM :: (Monad m) => (a -> Bool) -> m a -> m [a]
spanM p a = do
  x <- a
  if p x then do xs <- spanM p a
                 return (x:xs)
         else return [x]

allows this ghci session, for example:

*Main> spanM (/= NoEvent) pollEvent 
[SomeEvent,SomeEvent,NoEvent]
Greg Bacon
very newbie friendly version, thank you too :)
+1  A: 

James Cook was so kind to extend monad-loops with this function:

unfoldWhileM  :: Monad  m => (a -> Bool) -> m a -> m [a]

used with SDL:

events <- unfoldWhileM (/= SDL.NoEvent) SDL.pollEvent
lambdor