Как получить данные из sqlite и ответ JSON с помощью Скотти?

Я пытаюсь построить простой блог, используя Haskell и Framework Scotty. Используя Model.hs у меня есть:

data Post = Post
    { id :: Int
    , tipo :: String
    , titulo :: String
    , conteudo :: String
    } deriving (Show, Generic)

Я уже создал схему с использованием sqlite и заполнил ее некоторыми данными, сейчас я пытаюсь получить эти данные с помощью этого метода в моем Storage.hs.

selectPosts :: Sql.Connection -> IO [M.Post]
selectPosts conn =
    Sql.query_ conn "select * from post" :: IO [M.Post]

Я собираюсь получить формат данных как json в моем файле Main.hs:

instance ToJSON M.Post
instance FromJSON M.Post

main :: IO ()
main = do
    putStrLn "Starting Server..."
    scotty 3000 $ do
        get "/" $ file "templates/index.html"
        get "/posts" $ do
            json posts where
            posts = withTestConnection $ \conn -> do
                S.selectPosts conn

Но я получаю IO [Model Post], и я не знаю, как сделать это как json, поэтому он продолжает получать эту ошибку:

No instance for (ToJSON (IO [Post])) arising from a use of ‘json’

Мой проект в GitHub для запуска просто использовать стек сборки и после стека GHCI. В здании я уже получаю эту ошибку.

1 ответ

Решение

В Haskell все функции чистые - так что-то вроде selectPosts, который должен выйти и сделать IO, чтобы поговорить с базой данных, не может просто сделать это и вернуть значение из базы данных. Вместо этого эти функции возвращают что-то типа IO a, который вы можете рассматривать как описание того, как выйти и сделать IO, чтобы получить значение типа a, Эти "действия ввода-вывода" могут быть составлены вместе, и одно из них может быть назначено main; во время выполнения RTS выполнит эти действия ввода-вывода.

Тем не менее, вы не сочиняете IO a значение, которое вы получите от selectPosts быть частью большего IO значение, которое в конечном итоге становится main; вы пытаетесь напрямую использовать его, подавая его в json, Это не сработает, потому что нет (хорошо / просто) способа преобразовать описание того, как сделать IO, в строку JSON.

Способ, которым Haskell имеет дело с составлением этих значений, заключается в абстракции, известной как "монада", которая также полезна во многих других случаях. do нотация может использоваться для написания этой монадической последовательности в очень естественном стиле. Вы не можете просто написать posts <- withTestConnection S.selectPosts здесь, потому что Скотти get функция принимает значение монадического ActionM тип, не IO, Однако оказывается, что ActionM в основном это куча других полезных вещей, наложенных поверх IOтак что должна быть возможность "поднять" действие ввода-вывода из selectPosts в Скотти ActionM монада:

get "/posts" $ do
  posts <- liftIO $ withTestConnection S.selectPosts
  json posts

Примечание: возможно, вы заметили, что я написал withTestConnection S.selectPosts вместо withTestConnection $ \conn -> do S.selectPosts conn, Как правило, если у вас есть do блок с одним выражением (не в форме x <- act), это то же самое, что и одно выражение вне do блок: \conn -> S.selectPosts conn, Кроме того, Haskell имеет тенденцию поощрять частичное применение: у вас есть S.selectPosts, которая является функцией Sql.Connection -> IO [M.Post], \conn -> S.selectPosts conn другая функция того же типа, которая передает соединение в selectPosts а затем возвращает тот же результат, что и selectPosts--- эта функция неотличима от selectPosts сам! Так что, если это все, что вам нужно внутри вашего withTestConnection, вы должны быть в состоянии упростить всю лямбду и do блок просто S.selectPosts,

Другие вопросы по тегам