0

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
lhcopetti
  • 67
  • 1
  • 5
  • 1
    What does it mean to "mutate the MonadSnap"? Mutable values can be mutated, whereas `MonadSnap` is a typeclass, which is not even a value. `modifyResponse` modifies the Response - it does so by calling a function which modifies the entire `SnapState`, and modifies only the portion of the state corresponding to the `Response`. – user2407038 Aug 23 '17 at 05:09
  • I don't think I phrased that correctly. My question is how can I mutate the underlying SnapState which is itself contained inside the Snap record without even receiving it as a parameter. I got carried away by saying MonadSnap because it was the only thing that I could see in the declaration that was not so clear to me – lhcopetti Aug 23 '17 at 12:38

2 Answers2

2

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.

4castle
  • 32,613
  • 11
  • 69
  • 106
  • Your answer about the indirection was especially enlightening. Another reference that actually helped me visualize what I was trying to understand comes from this question: https://stackoverflow.com/questions/39807736/how-can-i-write-this-simple-code-using-the-state-monad?rq=1. The same way as `modifyResponse` does, the `addToState` acts upon the state without actually receiving it as a parameter. I guess that is a pattern I should get accustomed to. – lhcopetti Aug 23 '17 at 14:38
1

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 #-}
Cirdec
  • 24,019
  • 2
  • 50
  • 100