I will preface this that my understanding through reading this thread is that effect systems seem better for doing things with IO; therefore my pure logging endeavours below might not put them in the best light but I’d like to share what I’ve learned so far.
===================================================
I’m currently making Snake in Haskell via Brick as a learning opportunity and decided I wanted to learn about effect systems and see what it was like to combine effect systems together with mtl/transformer-style. To understand 1) Can I make a logging system with a known name? and 2) if I can, how does it work alongside Brick?
Brick’s EventM type is a big ReaderT of stuff and I wanted to do logging on the side but without adding to the overall stack of Monad Transformers.
I choose Bluefin as the one to learn and came up with this for my logging solution.
My initial plan was to have the whole Eff es Eventlist
as a record in my overall GameplayState
type because then later, I can call runPureEff
and extract the Eventlist
out of it. I couldn’t get this work by adding Eff e Eventlist
to the GameplayState
type as then it becomes GameplayState e
and I struggled to get logInGame
function to be happy when called in my eventHandler
function (some type stuff, couldn’t figure it out why it wasn’t happy).
So right now I just build the list at the time of usage.
I thread it into my main program like so:
runLogger :: (forall e. Logger g e -> Eff (e :& es) r) -> g -> Eff es EventList
runLogger f gps =
execWriter $ \writ -> do
runReader gps $ \rea -> do
useImplIn f $ Logger (mapHandle writ) (mapHandle rea)
...
handleMovement :: (e1 :> es) => ([a1] -> Either a2 (K.KeyDispatcher KeyEvent m)) -> BrickEvent n e2 -> Logger GameplayState e1 -> Eff es ()
handleMovement disp ev (Logger writ readstate) = do
gameplaystate <- ask readstate
let logaction = getKeyEvent disp altConfig ev
tick = gameplaystate ^. tickNo
addToLog writ tick logaction
...
logInGame :: BrickEvent n e2 -> GameplayState -> EventList
logInGame ev gs = runPureEff $ runLogger (handleMovement gameplayDispatcher ev) $ gs
...
eventHandler :: BrickEvent MenuOptions Tick -> EventM MenuOptions GameplayState ()
eventHandler ev = do
glf <- use gameLog
gps <- get
case gs of
...
Playing _ -> do
zoom gameState $ handleGameplayEvent' ev
gameLog .= (logInGame ev gps <> glf)
tickNo %= (+1)
...
However, for me this solution feels like cheating because at the end of the day, all my Logger es
is doing is generating a singleton event to append onto the front of my EventList
which is a linked list and I’m just appending them onto the front of the list by calling a function ==> a lot of work for (++)
- which really should become a (:)
in this current state - and function to pattern match on KeyEvents
. Still, a lot has been learned so far.
Ideally, it would be nice to get the Logger e
or the Eff e
into the GameplayState
type but I can’t figure it out right now.
I’m still experimenting around and trying to understand it and I have nowhere near the capability as anyone used to working on this stuff on a regular basis :’) so apologies if I have butchered anything.