I'm trying to build a simple blog using Haskell and the Framework Scotty. Using a Model.hs I have:
data Post = Post
{ id :: Int
, tipo :: String
, titulo :: String
, conteudo :: String
} deriving (Show, Generic)
I've already create a schema using sqlite and populate with some data, right now I'm trying to get this data using this method in my Storage.hs
selectPosts :: Sql.Connection -> IO [M.Post]
selectPosts conn =
Sql.query_ conn "select * from post" :: IO [M.Post]
My intent is get the data format as json in my 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
But I'm getting an IO [Model Post] and I don't know how to render this as json, so its keeping getting this error:
No instance for (ToJSON (IO [Post])) arising from a use of ‘json’
My project is in github to run just use stack build and after stack ghci. In the building I'm already getting this error.
In Haskell, all functions are pure---so something like selectPosts
, which needs to go out and do IO to talk to a database, can't just do that and return the value from the database. Instead, these kinds of functions return something of type IO a
, which you can think of as basically a description of how to go out and do IO to get a value of type a
. These "IO actions" can be composed together, and one of them can be assigned to main
; at runtime, the RTS will execute these IO actions.
However, you aren't composing the IO a
value that you get back from selectPosts
to be part of the larger IO
value that eventually becomes main
; you are trying to directly use it by feeding it into json
. This won't work, because there's no (good/easy) way to convert a description of how to do IO into a JSON string.
The way that Haskell deals with composing these values is through an abstraction known as a "monad", which is useful in a lot of other cases as well. do
notation can be used to write this monadic sequencing in a very natural style. You can't just write posts <- withTestConnection S.selectPosts
here because Scotty's get
function takes a value of the monadic ActionM
type, not IO
. However, it turns out that ActionM
is basically a bunch of other useful stuff layered on top of IO
, so it should be possible to "lift" the IO action from selectPosts
into Scotty's ActionM
monad:
get "/posts" $ do
posts <- liftIO $ withTestConnection S.selectPosts
json posts
Side note: You may have noticed that I wrote withTestConnection S.selectPosts
instead of withTestConnection $ \conn -> do S.selectPosts conn
. Generally, if you have a do
block with only one expression in it (not of the form x <- act
), this is the same as the single expression outside of a do
block: \conn -> S.selectPosts conn
. Furthermore, Haskell tends to encourage partial application: you have S.selectPosts
, which is a function Sql.Connection -> IO [M.Post]
. \conn -> S.selectPosts conn
is another function, of the same type, which passes the connection into selectPosts
and then returns the same result as selectPosts
---this function is indistinguishable from selectPosts
itself! So if this is all that you need inside your withTestConnection
, you should be able to simplify the whole lambda and do
block to just S.selectPosts
.