1

我又回来尝试学习 Haskell,哦,天哪,这很难!我正在尝试在 Scotty 端点内进行简单的 mongoDB 插入。问题是在 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

欢迎任何帮助或提示!

4

1 回答 1

2

我看到与该代码不同的错误消息。也许你做了一些改变(比如添加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动作,类型IO a为 some a。但是,该表达式insertLog id decodedBody并不代表 IO 操作。Action m Value对于一些mMonadIO约束的人来说,这是 Mongo 类型的动作。您需要使用一些函数运行 MongoActionIO。看起来您已经编写了这样一个函数,名为run. 它是为一般人编写的,MonadIO m但可以专门用于:

run :: Action IO a -> IO a

因此,如果您首先运行 Mongo 动作(将其变为IO)然后解除该动作(在 Scotty 动作中运行它post),则应键入以下内容:

i <- liftIO $ run $ insertLog id decodedBody

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

-- 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 操作转换为许多可能的 monad,方法是返回m a任何m支持MonadIO m. 因为您给出了insertLog一个带有返回类型的类型签名MonadIO m' => Action m' Value(我将变量更改为保持mm'不同),所以类型检查器将返回类型与返回类型相run匹配insertLog

m a ~ Action m' Value

通过设置a ~ Valuem ~ Action m'。因此,您的runininsertLog实际上与以下奇怪的类型一起使用:

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

通常,这会导致类型错误,但类型insert也是灵活的。它没有返回一个类型Action IO Value为“通常”的类型的动作,而是愉快地调整自己以返回一个类型的动作Action (Action IO) Value以匹配所run期望的。

于 2020-08-01T15:21:47.390 回答