Question

I am writing a simple game - Tetris. For the first time in my life I'm using functional programming for that goal, as a language I chose Haskell. However, I'm tainted with OOP and imperative thinking and scared of unconsciously applying this mindset to my Haskell program.

Somewhere in my game, I need to have information about elapsed time (Timer) and pressed/down keys (Keyboard). The approach used in SDL lessons translated to Haskell looks like that:

Main.hs

data AppData = AppData {
    fps :: Timer 
    --some other fields    
}

getFPS :: MonadState AppData m => m Timer
getFPS = liftM fps get

putFPS :: MonadState AppData m => Timer -> m ()
putFPS t = modify $ \s -> s { fps = t }

modifyFPSM :: MonadState AppData m => (Timer -> m Timer) -> m ()
modifyFPSM act = getFPS >>= act >>= putFPS

Timer.hs

data Timer = Timer { 
    startTicks :: Word32,
    pausedTicks :: Word32,
    paused :: Bool,
    started :: Bool
}

start :: Timer -> IO Timer
start timer = SdlTime.getTicks >>= \ticks -> return $ timer { startTicks=ticks, started=True,paused=False }

isStarted :: Timer -> Bool
isStarted Timer { started=s } = s

And then used like that: modifyFPSM $ liftIO . start. That makes Timer somewhat pure (it is not explicitly a monad, and its functions return IO only because it's required to measure time). However, that litters the code outside Timer module with getters and setters.

My approach used in Keyboard.hs is:

data KeyboardState = KeyboardState {
    keysDown :: Set SDLKey, -- keys currently down
    keysPressed :: Set SDLKey -- keys pressed since last reset 
};

reset :: MonadState KeyboardState m => m ()
reset = get >>= \ks -> put ks{keysPressed = Data.Set.empty} 

keyPressed :: MonadState KeyboardState m => SDLKey -> m ()
keyPressed key = do
     ks <- get 
     let newKeysPressed = Data.Set.insert key $ keysPressed ks
     let newKeysDown = Data.Set.insert key $ keysDown ks
     put ks{keysPressed = newKeysPressed, keysDown = newKeysDown}

keyReleased :: MonadState KeyboardState m => SDLKey -> m ()
keyReleased key = do
     ks <- get 
     let newKeysDown = Data.Set.delete key $ keysDown ks
     put ks{keysDown = newKeysDown}

That makes the module self-contained, but I'm afraid that this is my way to express object from OOP in Haskell and ruins the whole point of FP. So my question is:

What is the proper way to do that? Or what are the other possibilities to approach such situation? And if you notice any other flaws (be it design or style problems) feel free to point that out.

Was it helpful?

Solution

Most programs have some notion of state. So you don't have to worry every time you use the State monad in some way shape or form. It is still purely function since you're essentially writing

Arg1 -> Arg2 -> State -> (State, Result)

But instead of writing your combinators of the state monad, instead consider writing them as simple pure functions and then using modify to inject them into the state monad.

reset :: KeyBoard -> KeyBoard
keyPressed :: Key -> KeyBoard -> KeyBoard
...

And then when you actually want state, these are easily used

 do
   nextKey <- liftIO $ magic
   modify $ keyPressed nextKey

And if you want to use them in pure functions, you're no longer dragging the whole state monad with them, making it a bit simpler to build up combinators.

TLDR: A little state is not bad, and can even make the code easier to understand, but dragging it into every part of your code is bad.

OTHER TIPS

Contrary to popular belief, the Haskell philosophy is not about eliminating state, but about making state explicit, encapsulating it and controlling it. Feel free to use the state monad to your heart's content, if it makes your code clearer.

Haskell is very good at abstraction, and will allow you to express the concepts you want in your game at a higher level that what you have here. You would probably like to look into "Functional Reactive Programming"

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top