lhcopetti
lhcopetti

Reputation: 67

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

Upvotes: 0

Views: 316

Answers (2)

castletheperson
castletheperson

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

Cirdec
Cirdec

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

Related Questions