haskellactionmonadshaskell-snap-framework

How can one mutate a state when returning an empty action


I have been learning haskell for some time and I just finished reading 'Learn you a Haskell for great good'. My question comes from an assignment I'm currently trying to complete. Basically, I have been using the Snap Framework and I'm currently having a hard time understanding how the state (in this case the Request + Response object, the MonadSnap) is mutated when making calls such the one below:

modifyResponse :: MonadSnap m => (Response -> Response) -> m ()

I can't quite figure out how the modifyResponse method mutates the underlying MonadSnap while only specifying it as a type constraint.

I've come across this similar concept while searching for the answer and I believe the same answer would apply if I wanted to keep a state and make the below functions work as intended, like the OP proposed in this answer:

instance M Monad where ...

-- Modify the counter and do not produce any result:
incrementCounter :: M ()
decrementCounter :: M ()

-- Get the current value of the counter
readCounter :: M Integer

Solution

  • Here's the source code for modifyResponse:

    modifyResponse :: MonadSnap m => (Response -> Response) -> m ()
    modifyResponse f = liftSnap $
         smodify $ \ss -> ss { _snapResponse = f $ _snapResponse ss }
    

    The only function in the MonadSnap typeclass is liftSnap, and the only pre-defined instance of the typeclass is the Snap monad (where it's defined as liftSnap = id).

    It's just a tiny bit of indirection in order to allow the function to work with other types that implement MonadSnap also. It makes working with Monad Transformer stacks much easier. The signature could have been:

    modifyResponse :: (Response -> Response) -> Snap ()
    

    Now, perhaps your next question is: "How does smodify work?"

    Here's the source code:

    smodify :: (SnapState -> SnapState) -> Snap ()
    smodify f = Snap $ \sk _ st -> sk () (f st)
    

    As you can see, it's not magic, just functions. The internal SnapState is being hidden from you, because you don't need to know the internals of Snap in order to use it. Just like how you don't need to know the internals of the IO monad in order to use it.