I am back again trying to learn Haskell and, oh boy it is difficult! I am a trying to do a simple mongoDB insertion inside a Scotty endpoint. Problem is the type return by the insert function is not accepted in the Scotty do statement. The program is quite simple:
{-# 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]
the problem comes in the line
i <- liftIO $ insertLog id decodedBody
And the type error is
Expected type: Web.Scotty.Internal.Types.ActionT
Data.Text.Internal.Lazy.Text IO Value
Actual type: Action m0 Value
Any help or tip will be welcome!
I see a different error message with that code. Maybe you made some changes (like adding 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
In the line:
i <- liftIO $ insertLog id decodedBody
the liftIO
function expects a genuine IO
action, of type IO a
for some a
. However, the expression insertLog id decodedBody
doesn't represent an IO action. It is Mongo action of type Action m Value
for some m
that has a MonadIO
constraint. You need to use some function run Mongo Action
values in IO
. It looks like you've already written such a function, named run
. It's written for a general MonadIO m
but can be specialized to:
run :: Action IO a -> IO a
so if you first run your Mongo action (to turn it into IO
) and then lift that action (to run it in the Scotty action under post
), the following should type check:
i <- liftIO $ run $ insertLog id decodedBody
Update: Whoops! I missed the run
in the insertLog
function. You either want to write:
-- 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]
OR you want to write:
-- 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]
That will avoid the double-run
problem.
The reason run
didn't work as intended in your original code is a little complicated...
The problem is that run
has flexibility to convert its Mongo action to many possible monads by returning m a
for any m
that supports MonadIO m
. Because you gave insertLog
a type signature with return type MonadIO m' => Action m' Value
(where I changed the variable to keep m
and m'
distinct), the type checker matched the return type of run
to the return type of insertLog
:
m a ~ Action m' Value
by setting a ~ Value
and m ~ Action m'
. So, your run
in insertLog
was actually used with the following bizarre type:
run :: Action (Action m') Value -> Action m' Value
Normally, this would have caused a type error, but the type of insert
is also flexible. Instead of returning an action of type Action IO Value
, which would be the "usual" type, it happily adapted itself to return an action of type Action (Action IO) Value
to match what run
was expecting.