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.