Reputation: 67
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
Upvotes: 0
Views: 316
Reputation: 33496
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.
Upvotes: 2
Reputation: 24166
Every instance of MonadSnap
must provide a liftSnap
method:
-- | 'MonadSnap' is a type class, analogous to 'MonadIO' for 'IO', that makes it -- easy to wrap 'Snap' inside monad transformers. class (Monad m, MonadIO m, MonadBaseControl IO m, MonadPlus m, Functor m, Applicative m, Alternative m) => MonadSnap m where -- | Lift a computation from the 'Snap' monad. liftSnap :: Snap a -> m a
The means that any time there's a MonadSnap m
it's easy to convert a concrete Snap a
into m a
via liftSnap
.
Modifying a concrete Snap
's state is done by succeeding with the new SnapState
and a unit ()
value. sk
is the success continuation for what to do on success.
-- | Local Snap monad version of 'modify'. smodify :: (SnapState -> SnapState) -> Snap () smodify f = Snap $ \sk _ st -> sk () (f st) {-# INLINE smodify #-}
modifyResponse
lifts smodify
from Snap a
to MonadSnap m => m a
. The function f
passed to smodify
is a function that modifies only the _snapResponse
field of the SnapState
record.
modifyResponse :: MonadSnap m => (Response -> Response) -> m () modifyResponse f = liftSnap $ smodify $ \ss -> ss { _snapResponse = f $ _snapResponse ss } {-# INLINE modifyResponse #-}
Upvotes: 1