3
votes

I would like to embed ReaderT into another monad transformer. How do I do this? The example below uses Scotty but I think it would be the same with any other monad.

{-# LANGUAGE OverloadedStrings #-}

import qualified Web.Scotty
import Web.Scotty.Trans

import Data.Text.Lazy
import Control.Monad.IO.Class (liftIO)

import Control.Monad.Trans.Reader
import Control.Monad.Trans

data Config = Config Text

main :: IO ()
main = do
    let config = Config "Hello World"
    -- how to I make this line work?
    scottyT 3000 id id routes

routes :: ScottyT Text (ReaderT Config IO) ()
routes = do
    get "/" info

info :: ActionT Text (ReaderT Config IO) ()
info = do
    -- this part seems like it works!
    Config message <- lift ask
    text $ "Info: " `append` message

This errors on the line scottyT 3000 id id routes, because scottyT expects a ScottyT Text IO (). How do I make this work? Here are the current errors:

Server.hs: line 24, column 24:
  Couldn't match type `ReaderT Config IO' with `IO'
    Expected type: ScottyT Text IO ()
      Actual type: ScottyT Text (ReaderT Config IO) ()
1
Well, this works but I'm not sure it's right: flip runReaderT config $ scottyT 3000 id (flip runReaderT config) routes. I don't think it's correct considering runReaderT is needed twice, but I don't know enough about scotty to know why the scottyT function requires that argumentbheklilr
Could you just change the order of the "Transformes stack" i.e. ReaderT Config (ScottyT Text IO) ()?felix-eku
Based off of this answer, you can do `scottyT 3000 (flip runReaderT) (flip runReaderT) routes, and that seems to be the way other people have done it.bheklilr
@chaosmasttter Unfortunately, no. The get function has the type (ScottyError e, MonadIO m) => RoutePattern -> ActionT e m () -> ScottyT e m (), instead of the more general (ScottyError e, MonadIO m, MonadAction a, MonadScotty s) => RoutePattern -> a e m () -> s e m ()bheklilr
@chaosmasttter I don't want to. I want to be able to use Scotty stuff without lifting.Sean Clark Hess

1 Answers

1
votes

You have to change the arguments you've supplied as id to be ones that have the type forall a. m a -> n a and m Response -> IO Response respectively. Why? I don't know, but the example I found here shows someone running it similar to

main = do
    let config = Config "Hello, world"
        runner = flip runReaderT config
    scottyT 3000 runner runner routes

I've tested it, and it at least works. Whether or not this is best practices is unknown to me. If someone has a better method, feel free to post it.