Как получить данные из 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
,