在ScottyT中使用ReaderT变换器(与ActionT相比)

时间:2018-01-16 17:39:10

标签: haskell monads monad-transformers scotty

我正在尝试使用ReaderT monad变换器方法通过我的基于Scotty的应用程序进行线程配置,并且无法这样做。我必须在定义路由时使用配置(因为其中一些取决于配置)和处理实际请求时。

后者在ActionT中运行得很好,但无论我尝试什么,我都无法在ScottyT中获得正确的类型。

这是我从Scotty GitHub存储库的ReaderT示例中编译的最小示例:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Applicative
import Control.Monad.Reader (MonadIO, MonadReader, ReaderT, asks, lift, runReaderT)
import Data.Default.Class (def)
import Data.Text.Lazy (Text, pack)
import Prelude
import Web.Scotty.Trans (ScottyT, get, scottyOptsT, text, capture)

data Config = Config
  { environment :: String
  } deriving (Eq, Read, Show)

newtype ConfigM a = ConfigM
  { runConfigM :: ReaderT Config IO a
  } deriving (Applicative, Functor, Monad, MonadIO, MonadReader Config)

application :: ScottyT Text ConfigM ()
application = do
  get "/" $ do
    e <- lift $ asks environment
    text $ pack $ show e

  path <- lift $ asks environment
  get (capture path) $ do
    text $ pack $ "Hello, custom path"

main :: IO ()
main = scottyOptsT def runIO application where
  runIO :: ConfigM a -> IO a
  runIO m = runReaderT (runConfigM m) config

  config :: Config
  config = Config
    { environment = "Development"
    }

我得到的错误是:

• No instance for (Control.Monad.Trans.Class.MonadTrans
                     (ScottyT Text))
    arising from a use of ‘lift’
• In a stmt of a 'do' block: path <- lift $ asks environment

我已经查看了ScottyT类型概述的代码,实际上似乎没有为它定义MonadTrans的实例。

然而,我觉得我没有足够的法术力和Haskell经验来找到解决方法并且会感激任何帮助!

谢谢!

1 个答案:

答案 0 :(得分:0)

以集体的心态,我们都找到了解决问题的当前可行解决方案。

ScottyT类型是https://github.com/scotty-web/scotty/pull/167合并后的monad变换器,因此目前无法以这种方式使用它。有一个PR https://github.com/scotty-web/scotty/pull/181旨在恢复该功能,但据我所知它从未合并过。

由于它不是monad变换器,我们只能再次包装它:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Applicative
import Control.Monad.Reader (MonadIO, MonadReader, ReaderT, asks, lift, runReaderT)
import Data.Default.Class (def)
import Data.Text.Lazy (Text, pack)
import Prelude
import Web.Scotty.Trans (ScottyT, get, scottyOptsT, text, capture)

data Config = Config
  { environment :: String
  } deriving (Eq, Read, Show)

newtype ConfigM a = ConfigM
  { runConfigM :: ReaderT Config IO a
  } deriving (Applicative, Functor, Monad, MonadIO, MonadReader Config)

application :: ConfigM (ScottyT Text ConfigM ())
application = do
  path <- asks environment

  return $
    get "/" $ do
      e <- lift $ asks environment
      text $ pack $ show e

    get (capture path) $          
      text $ pack $ "Hello, custom path"

runIO :: Config -> ConfigM a -> IO a
runIO c m = runReaderT (runConfigM m) c

main :: IO ()
main = do
  let config = Config { environment = "/path" }
  app <- runIO config application
  scottyOptsT def (runIO config) app

感谢大家帮助我,希望这有助于像我这样的另一个游荡的Scotty :)。