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,只有路由处理函数的ActionMmonad才具备该实例。 inAppDb的设计逻辑错误:dbFunction接受数据库查询并返回IO类型结果,却试图用liftIO将其直接嵌入顶层ScottyT上下文,违背了Scotty的API约束。
具体修复步骤
调整初始化操作的执行方式
如果是在启动时执行数据库初始化(比如查询数据做预热),需要用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]修正路由内的数据库操作调用
如果需要在路由回调中复用数据库逻辑,重新定义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完善类型标注
给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
相关产品推荐
相关产品推荐

