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
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.