Haskell 对 IO 的 scotty 操作

Haskell scotty Action to IO

我又回来努力学习 Haskell 而且,天哪,这很难! 我正在尝试在 Scotty 端点内进行简单的 mongoDB 插入。问题是插入函数的类型 return 在 Scotty do 语句中不被接受。程序很简单:

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Data.Monoid (mconcat)
import Control.Monad.Trans(liftIO,lift,MonadIO)
import System.IO
import Data.Text.Lazy.Encoding (decodeUtf8)
import Data.Text.Lazy (pack,unpack)
import Data.Maybe
import Data.Time.Clock.POSIX
import Database.MongoDB    (Action, Document, Document, Value, access,
                            allCollections,insert, close, connect, delete, exclude, find,
                            host,findOne, insertMany, master, project, rest,
                            select, liftDB, sort, Val, at, (=:))

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

    post "/logs" $ do
       id <- liftIO $ getTimeInMillis
       b <- body
       let decodedBody = unpack(decodeUtf8 b)
       i <- liftIO $ insertLog id decodedBody
       text $ "Ok"

--setup database connection
run::MonadIO m => Action m a -> m a 
run action = do
        pipe <- liftIO(connect $ host "127.0.0.1")
        access pipe master "data" action

getTimeInMillis ::Integral b => IO b
getTimeInMillis = round `fmap` getPOSIXTime

insertLog::MonadIO m => Int -> String -> Action m Value
insertLog id body = run $ insert "logs" ["id" =: id, "content" =: body]

问题出在行

 i <- liftIO $ insertLog id decodedBody

类型错误是

 Expected type: Web.Scotty.Internal.Types.ActionT
                       Data.Text.Internal.Lazy.Text IO Value
 Actual type: Action m0 Value

欢迎任何帮助或提示!

我看到带有该代码的不同错误消息。也许您做了一些更改(例如添加 liftIO)。

• Couldn't match type ‘Control.Monad.Trans.Reader.ReaderT
                         Database.MongoDB.Query.MongoContext m0 Value’
                 with ‘IO a0’
  Expected type: IO a0
    Actual type: Action m0 Value

行中:

i <- liftIO $ insertLog id decodedBody

liftIO 函数需要一个真正的 IO 动作,对于某些 a,类型为 IO a。但是,表达式 insertLog id decodedBody 并不表示 IO 操作。对于某些具有 MonadIO 约束的 m,它是 Mongo 类型 Action m Value 的操作。您需要在 IO 中使用一些函数 运行 Mongo Action 值。看起来你已经写了这样一个函数,命名为run。它是为一般 MonadIO m 编写的,但可以专门用于:

run :: Action IO a -> IO a

所以如果你先 运行 你的 Mongo 动作(把它变成 IO)然后解除那个动作(到 运行 它在 Scotty 动作下post), 以下应键入检查:

i <- liftIO $ run $ insertLog id decodedBody

更新:哎呀!我错过了 insertLog 函数中的 run。你要么写:

-- use "run" here
main = do
   ...
   i <- liftIO $ run $ insertLog id decodedBody

-- but no "run" here
insertLog::MonadIO m => Int -> String -> Action m Value
insertLog id body = insert "logs" ["id" =: id, "content" =: body]

你想写:

-- no "run" here
main = do
   ...
   i <- liftIO $ insertLog id decodedBody

-- change the type signature and use "run" here
insertLog :: Int -> String -> IO Value
insertLog id body = run $ insert "logs" ["id" =: id, "content" =: body]

这将避免双重 run 问题。

run 在您的原始代码中没有按预期工作的原因有点复杂...

问题是 run 可以灵活地将其 Mongo 动作转换为许多可能的单子,方法是 returning m a 支持任何 m MonadIO m。因为你给 insertLog 一个类型签名 return 类型 MonadIO m' => Action m' Value (我改变了变量以保持 mm' 不同),类型检查器匹配return 类型 run 到 return 类型 insertLog:

m a ~ Action m' Value

通过设置a ~ Valuem ~ Action m'。所以,你在 insertLog 中的 run 实际上使用了以下奇怪的类型:

run :: Action (Action m') Value -> Action m' Value

通常这会导致类型错误,但insert的类型也是灵活的。它不是 return 类型 Action IO Value 的动作,这将是“通常”类型,它愉快地适应 return 类型 Action (Action IO) Value 的动作以匹配 run 期待。