Haskell Скотти: Действие для ввода-вывода - PullRequest
1 голос
/ 01 августа 2020

Я снова вернулся, пытаясь выучить Haskell, и, о боже, это сложно! Я пытаюсь выполнить простую вставку 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

Любая помощь или подсказка будут добро пожаловать!

1 Ответ

2 голосов
/ 01 августа 2020

Я вижу другое сообщение об ошибке с этим кодом. Возможно, вы внесли какие-то изменения (например, добавили 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 для некоторых a. Однако выражение insertLog id decodedBody не представляет собой действие ввода-вывода. Это действие Mon go типа Action m Value для некоторых m, которые имеют ограничение MonadIO. Вам нужно использовать некоторую функцию, запускающую Mon go Action значения в IO. Похоже, вы уже написали такую ​​функцию с именем run. Он написан для общего MonadIO m, но может быть специализирован на:

run :: Action IO a -> IO a

, поэтому, если вы сначала запустите действие Mon go (чтобы превратить его в IO), а затем поднять это действие (чтобы запустите его в действии Скотти под 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]

Это позволит избежать проблемы double- run.

Причина, по которой run не работает так, как предполагалось в вашем исходном коде, немного сложна ...

Проблема в том, что run имеет гибкость для преобразования своего действия Mon go во многие возможных монад, возвращая m a для любого m, поддерживающего MonadIO m. Поскольку вы дали insertLog сигнатуру типа с возвращаемым типом MonadIO m' => Action m' Value (где я изменил переменную, чтобы m и m' были разными), средство проверки типов сопоставило возвращаемый тип run с возвращаемым типом insertLog:

m a ~ Action m' Value

, задав a ~ Value и m ~ Action m'. Итак, ваш run in insertLog фактически использовался со следующим странным типом:

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

Обычно это вызвало бы ошибку типа, но тип insert также является гибким. Вместо того, чтобы возвращать действие типа Action IO Value, которое было бы «обычным» типом, он с радостью адаптировался, чтобы возвращать действие типа Action (Action IO) Value, соответствующее тому, что ожидал run.

...