You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scotty框架使用liftIO报错:No instance for MonadIO ScottyT

问题

自学Haskell时参考旧教程代码开发,运行stack run后出现类型错误,错误指向liftIO .处。

使用的代码:

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Products
  name Text
  description Text
  price Int
  deriving Show
|]

main :: IO ()
main = scotty 3000 $ do
  Web.Scotty.middleware logStdoutDev
  inAppDb $ do
    doDbStuff
  Web.Scotty.get "/api/products" $ json [(0::Int)..10]

inAppDb = liftIO . dbFunction

dbFunction query = runStderrLoggingT $
        withPostgresqlPool connStr 10 $
        \pool -> liftIO $ runSqlPersistMPool query pool

doDbStuff = do
  res  :: [Entity Products] <- selectList [] [LimitTo 1]
  liftIO $ print res

运行错误信息:

➜  my-project stack run
my-project> build (lib + exe)
Preprocessing library for my-project-0.1.0.0..
Building library for my-project-0.1.0.0..
ld: warning: -single_module is obsolete
Preprocessing executable 'my-project-exe' for my-project-0.1.0.0..
Building executable 'my-project-exe' for my-project-0.1.0.0..
[1 of 3] Compiling Main [Source file changed]

/Users/home/my-project/app/Main.hs:176:11: error:
    • No instance for (Control.Monad.IO.Class.MonadIO
                         (Web.Scotty.Internal.Types.ScottyT
                            Data.Text.Internal.Lazy.Text IO))
        arising from a use of ‘liftIO’
    • In the first argument of ‘(.)’, namely ‘liftIO’
      In the expression: liftIO . dbFunction
      In an equation for ‘inAppDb’: inAppDb = liftIO . dbFunction
    |
176 | inAppDb = liftIO . dbFunction
    |           ^^^^^^

Error: [S-7282]
       Stack failed to execute the build plan.

       While executing the build plan, Stack encountered the error:

       [S-7011]
       While building package my-project-0.1.0.0 (scroll up to its section to see the error) using:
       /Users/home/.stack/setup-exe-cache/aarch64-osx/Cabal-simple_6HauvNHV_3.8.1.0_ghc-9.4.7 --verbose=1 --builddir=.stack-work/dist/aarch64-osx/ghc-9.4.7 build lib:my-project exe:my-project-exe --ghc-options " -fdiagnostics-color=always"
       Process exited with code: ExitFailure 1

解决思路

错误核心原因

错误提示ScottyT没有MonadIO实例,因为:

  • 在Scotty的顶层配置块(scotty 3000 $ do ...内的代码)中直接使用liftIO,但顶层上下文是用于配置路由、中间件的,并不支持MonadIO,只有路由处理函数的ActionM monad才具备该实例。
  • inAppDb的设计逻辑错误:dbFunction接受数据库查询并返回IO类型结果,却试图用liftIO将其直接嵌入顶层ScottyT上下文,违背了Scotty的API约束。

具体修复步骤

  1. 调整初始化操作的执行方式
    如果是在启动时执行数据库初始化(比如查询数据做预热),需要用Scotty提供的liftAndCatchIO替代liftIO,它专门用于顶层上下文执行IO操作:

    main :: IO ()
    main = scotty 3000 $ do
      Web.Scotty.middleware logStdoutDev
      -- 替换liftIO为liftAndCatchIO执行初始化
      liftAndCatchIO $ dbFunction doDbStuff
      Web.Scotty.get "/api/products" $ json [(0::Int)..10]
    
  2. 修正路由内的数据库操作调用
    如果需要在路由回调中复用数据库逻辑,重新定义inAppDb适配ActionM上下文:

    -- 明确类型签名,让inAppDb适配路由的ActionM monad
    inAppDb :: SqlPersistT IO a -> ActionM a
    inAppDb query = liftIO $ dbFunction query
    
    -- 在路由中调用示例
    Web.Scotty.get "/api/products" $ do
      products <- inAppDb $ selectList [] []
      json products
    
  3. 完善类型标注
    给dbFunction加上明确的类型签名,避免类型推断歧义:

    dbFunction :: SqlPersistT IO a -> IO a
    dbFunction query = runStderrLoggingT $
      withPostgresqlPool connStr 10 $
      \pool -> runSqlPersistMPool query pool
    

额外说明

旧教程可能基于早期版本的Scotty,当时的API允许顶层使用liftIO,但新版本已调整约束,因此需要适配当前Scotty的API规范——顶层仅做配置,IO操作要么用liftAndCatchIO执行初始化,要么放到路由回调的ActionM上下文中。

内容的提问来源于stack exchange,提问作者Aron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 12:52:44