Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stateful code under another monad

I have a hobby web project. Very simple, just to learn Haskell and web programming. For clarity, I use Snap framework. And I have the following code (site.com/auth handler):

auth :: MonadSnap m => m ByteString  
auth = withSession $ \s -> do  
    Just user <- getPostParam "user"
    Just password <- getPostParam "password"
    if user == "demi" && password == "1234"
       then redirect "/"
       else redirect "/login"

withSession reads current session and runs the function in the parameter. Here I face a problem: user gets authorized and I want to put new value to session s and run code with it. What is the best way to do it? How will you do it? Assume that code below also uses s.

Another question: can I somehow make context available transparently in the handler (like auth) and other functions? I don't want to pull all the context (like DB connection, session and probably other) in all functions with parameter like ctx:

findGoodies :: MonadSnap m => MyContext -> String -> m String
checkCaptcha :: MonadSnap m => MyContext -> m Bool
breakingNews :: MonadSnap m => MyContext -> m ByteString

Ideally, I want to have a function withContext but context may be changed during handling a request. I think I may solve it defining my monad (right?), but I already have to use Snap monad and I can't extend it (this is a question too)?

Hope I tell it pretty clear to help me.

like image 379
demi Avatar asked Sep 25 '26 07:09

demi


1 Answers

You can wrap your MonadSnap monad in a StateT that has your context as its state. Once the appropriate instances have been defined, you can write functions in your new monad that have access to the session state but can still invoke MonadSnap functions without lift.

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
import Control.Monad.State

-- StateT wrapper
newtype MySnapT m a = MySnapT { unMySnapT :: StateT MyContext m a }
    deriving ( Monad )

instance MonadTrans MySnapT where
    lift = MySnapT . lift

instance MonadSnap m => MonadSnap (MySnapT m) where
    liftSnap = lift . liftSnap

instance MonadSnap m => MonadState MyContext (MySnapT m) where
    get = MySnapT get
    put = MySnapT . put

runMySnapT :: MonadSnap m => MySnapT m a -> MyContext -> m (a, MyContext)
runMySnapT m = runStateT . unMySnapT $ m

-- wrapper for withSession that runs a MySnapT action with
-- the current session as the StateT state, and sets the
-- resulting state back when it is done
withMySession :: MonadSnap m => MySnapT m a -> m a
withMySession m = do
    (a, s') <- withSession $ runMySnapT m -- read the session and run the action
    setSession s' -- write the session back to disk
    return a        



-- functions that run in the MySnapT monad have access to context as
-- state, but can still call MonadSnap functions
findGoodies :: MonadSnap m => String -> MySnapT m String
findGoodies s = do
    s <- get -- get the session
    put $ modifySession s -- modify and set the session back into the State
    liftSnap undefined -- I can still call Snap functions
    return "Hello"

auth :: MonadSnap m => m String  
auth = withMySession $ do -- use withMySession to run MySnapT actions
    findGoodies "foo"


-- dummy definitions for stuff I don't have

data Snap a = Snap a

class Monad m => MonadSnap m where
  liftSnap :: Snap a -> m a

data MyContext = MyContext

withSession :: MonadSnap m => (MyContext -> m a) -> m a
withSession = undefined

setSession :: MonadSnap m => MyContext -> m ()
setSession = undefined

modifySession :: MyContext -> MyContext
modifySession = undefined
like image 145
pat Avatar answered Sep 27 '26 00:09

pat



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!