5
votes

In the question Web, Scotty: connection pool as monad reader it is shown how to use ScottyT to embed a Reader monad in the stack to access a static configuration (in that case, a connection pool).

I have a similar question, but simpler – or at least I thought so…

I want to add a Reader to a single handler (i.e. a ActionT), not the whole app.

I started modifying the program from the question above, but I cannot figure out how to turn an ActionT Text (ReaderT String IO) into the ActionT Text IO the handler needs to be. After fumbling around and trying to use typed holes hoping to see how to construct this I have to give up for now and ask for help. I really feel this should be simple, but cannot figure out how to do this.

Here's the program, with the lines where I'm stuck highlighted:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text.Lazy as T
import           Data.Text.Lazy (Text)
import           Control.Monad.Reader
import           Web.Scotty.Trans

type ActionD = ActionT Text (ReaderT String IO)

main :: IO ()
main = do
  scottyT 3000 id id app

-- Application
app ::  ScottyT Text IO ()
app = do
  get "/foo" $ do
    h <- handler              -- ?
    runReaderT h "foo"        -- ?
--get "/bar" $ do
--  h <- handler
--  runReaderT h "bar"

-- Route action handler
handler ::  ActionD ()
handler = do
  config <- lift ask
  html $ T.pack $ show config
1
Interesting. I have no idea about how to do this in a generic way -- it is possible that this requires to tear apart the ActionT and rebuild it. However, that type is opaque, so we can not...chi
Oh. Hmm. Maybe you're right. I could of course explicitly pass the config to the handlers and from there on to the functions they call, but I had this vague idea that whenever one does that, Reader can be used instead.beta
The issue seems to be turning t1 (t2 m) a into t1 m a, where t1,t2 are monad transformers. Or, more generically, t m1 a into t m2 a where m1,m2 are monads. This looks as a functor-ish requirement on t: given f :: forall b. m1 b -> m2 b you would like to have fmapT f :: t m1 a -> t m2 a. Maybe not all transformers would allow fmapT but this could be a typeclass on its own. Or maybe there's some way to craft it with standard tools I am not seeing right now :-/chi

1 Answers

5
votes

If you want to run each action in a separate reader, you don't need the more complex Scotty.Trans interface at all. You can just build you monad stack the other way around, with ReaderT on top.

import qualified Data.Text.Lazy as T
import           Control.Monad.Reader
import           Web.Scotty

type ActionD = ReaderT String ActionM

main :: IO ()
main = do
  scotty 3000 app

-- Application
app ::  ScottyM ()
app = do
  get "/foo" $ do
    runReaderT handler "foo"

-- Route action handler
handler ::  ActionD ()
handler = do
  config <- ask
  lift $ html $ T.pack $ show config