Run Persistent queries inside a module - haskell

Keeping in mind that I never succeded in understanding what a monad is, I'm having a problem with Yesod. I have a form that I would need to use in multiple Handlers, so I'm trying to define it in a module that I could import when needed. That form is using a bunch of not exported functions to create custom Fields.
When trying to run simple database operation like I'm used to inside my Handlers (trying to get default values for the fields), I get the following error :
Couldn't match expected type ‘Maybe (Entity Client)’
with actual type ‘HandlerT site0 IO (Maybe (Entity Client))’
In a stmt of a 'do' block: runDB $ selectFirst [ClientId ==. id] []
In the expression: do { runDB $ selectFirst [ClientId ==. id] [] }
In a case alternative:
Just id -> do { runDB $ selectFirst [ClientId ==. id] [] }
I tried a bunch of stuff, using <- doesn't seem to change anything. I think that I might not be inside the correct monad ? I googled a lot and as I understand it, runDB will only work inside the Handler monad, but I have no idea how to get in that, if at all possible.
I have multiple Handlers importing that module, calling that function to generate an AForm, if that matters.
Thanks a lot !
EDIT :
Here is the function raising that error :
getClientFromId :: Maybe (Key Client) -> Maybe (Entity Client)
getClientFromId mId = do
case mId of
Nothing -> Nothing
Just id -> do
runDB $ selectFirst [ClientId ==. id] []
Update
So after reading the answers, I tried removing the signature : no change.
I tried using the different suggested signatures but nothing worked. Here is what I have currently :
getClientFromId :: Maybe (Key Client) -> Handler (Maybe (Entity Client))
getClientFromId mId = do
case mId of
Nothing -> return Nothing
Just id -> do
client <- runDB $ selectFirst [ClientId ==. id] []
return client
I'm calling that from a ternary inside the definition of an AForm :
<*> (fmap entityKey <$> aopt clientIdField (addIdToField "ClientIdField" (bfs ("Owner" :: Text))) (isJust mHorse ? (Just (getClientFromId $ horseClient $ fromJust mHorse)) :? Nothing))
The ternary isn't very easy to read, if I figure out how to make all of this work I'll probably change that :).
I get that error :
Couldn't match type ‘HandlerT App IO (Maybe (Entity Client))’
with ‘Maybe (Entity Client)’
Expected type: Maybe (Entity Client)
Actual type: Handler (Maybe (Entity Client))
In the first argument of ‘Just’, namely
‘(getClientFromId $ horseClient $ fromJust mHorse)’
In the first argument of ‘(:?)’, namely
‘(Just (getClientFromId $ horseClient $ fromJust mHorse))’
In the second argument of ‘(?)’, namely
‘(Just (getClientFromId $ horseClient $ fromJust mHorse))
:? Nothing’
Looking at that, it seems that I have the correct return types except there is a "HandlerT site0 IO" on top of it. I tried using an intermediary function with a <- to remove it but that's not working. I'm guessing my actual problem here is that I don't understand monads, but I looked at other Yesod projects that work and I really don't get what I'm doing differently.
(I know that's nesting multiple Maybes, but in my DB horseClient is a maybe, and the default value of a form is waiting for a Maybe, so it seems "normal" that the final value should be a Maybe (Maybe (Entity Client)) even if that's a bit strange)

You declared getClientId as a pure function, but you are also calling runDB which performs I/O, so it can't be a pure function. Intuitively that's why it doesn't type check.
I suggest that you write getClientId in isolation without a type signature and see what GHC infers. My guess is that it will be something like:
Maybe (Key Client) -> Handler (Maybe (Entity Client))
That means everywhere you call it you'll need to do something like:
mid' <- getClientFromId mid

Let's look at the types.
selectFirst :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m) =>
[Filter val] ->
[SelectOpt val] ->
m (Maybe (Entity val))
runDB :: YesodDB site a -> HandlerT site IO a
type YesodDB site = ReaderT (YesodPersistBackend site) (HandlerT site IO)
The m in your selectFirst will be YesodDB site which is an alias for a ReaderT that I'm guessing provides the database connection.
I think the correct type for your function should be
Maybe (Key Client) -> HandlerT site IO (Maybe (Entity Client))
site will be replaced with your Yesod instance type.
update:
First of all, this is pretty ugly:
(isJust mHorse ? (Just (getClientFromId $ horseClient $ fromJust mHorse)) :? Nothing)
You could do this instead:
fmap (justClientFromId . horseClient) mHorse
However, that brings us to the second problem. justClientFromid returns a Handler wrapping the value you want.
I think the main problem stems from your first statement:
Keeping in mind that I never succeded in understanding what a monad is
You should probably read the Yesod documentation and try to understand what's going on.

Related

Haskell Monads and the liftIO I don't get it

Hello community thank you for your time.
I have an error and I am not sure what the error is, but what I think the problem is:
There is no IO transformer from ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO) to Web.Scotty.Internal.Types.ScottyT.
But I wondering why the compiler works with ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO). That's why I am working just with String and I removed all occurrences of {-# LANGUAGE OverloadedStrings #-} but still get the error. On the other hand, this should be IO [String], shouldn't it?
And as you can mention I don't really know what ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO) is.
At another place, I already use liftIO successfully for an a -> IO String function. And I think I use them the same way.
I think I get slowly a feeling for what a monad is, but not quite sure. I don't really know why I have to use a lift function at all.
Error message:
• No instance for (MonadIO
(Web.Scotty.Internal.Types.ScottyT
text-1.2.4.1:Data.Text.Internal.Lazy.Text IO))
arising from a use of ‘liftIO’
• In a stmt of a 'do' block:
paths <- liftIO $ getAllFilePaths2 path
In the expression:
do paths <- liftIO $ getAllFilePaths2 path
pathsToScotty paths
In an equation for ‘pathsToScotty2’:
pathsToScotty2 path
= do paths <- liftIO $ getAllFilePaths2 path
pathsToScotty paths
|
49 | paths <- liftIO $ getAllFilePaths2 path
Where the error occurred:
import Control.Monad.IO.Class
...
pathsToScotty2 :: String -> ScottyM ()
pathsToScotty2 path = do
paths <- liftIO $ getAllFilePaths2 path
pathsToScotty paths
getAllFilePaths2 :: String -> IO [String]
getAllFilePaths2 dir = do
putStrLn dir
isFile <- doesFileExist dir
if isFile
then return [dir]
else do
dirs <- listDirectory dir
foldl foldHelper2 (return []) $ map (\d -> show $ mconcat [dir, "/",d ]) dirs
foldHelper2 :: IO [String] -> String -> IO [String]
foldHelper2 ps path = do
paths <- ps
newPaths <- getAllFilePaths2 path
return (paths ++ newPaths)
Truly understanding monads takes time, practice, and patience, but it shouldn't be too hard to understand the need for liftIO by examining your types.
First off, the type of liftIO is MonadIO m => IO a -> m a. This means that the function can convert any IO action into an action in the monad m so long as m has an instance of MonadIO. In theory, this can only be implemented if m has some way of processing IO actions, so this function is embedding the given action into the m monad.
You're definitely in the right sort of place to use liftIO, so why isn't it working? That is, you have a value getAllFilePaths2 path of type IO [String], and you'd like it to be a value of type ScottyM [String] — this indeed seems like a good place to use liftIO. However, ScottyM is not an instance of MonadIO, as that error message you saw is trying to tell you, so you can't use liftIO.
This may seem crazy—can you really not embed IO actions into ScottyM?—but there's actually a good reason for this. What happens if the IO action throws an error? Does your whole web app crash? It would if you naively used liftIO. Instead, scotty provides the function liftAndCatchIO, which, as the docs describe, is "Like liftIO, but catch any IO exceptions and turn them into Scotty exceptions." This is the preferred way to embed IO actions into Scotty.
And here comes the final gotcha: Note that liftAndCatchIO actually produces values of type ActionM a, not ScottyM a. Additionally, there's no way to take a value in the ActionM monad and get it into the ScottyM monad. Instead, you need to use that value as an action. So, I'm not sure what pathsToScotty does, but it's very likely that you'll need to rewrite it.

Yesod: Using Github API v3 Library for Haskell

I'm working on a project that builds on the simple yesod template. I am new to functional programming, haskell and Yesod so it's probably something obvious to anyone with Yesod experience. At the moment I am trying to make github API calls using this library. I am getting some type issues and I'm not even sure how to start approaching solving them.
You can find my handler here.
Handler/Home.hs:43:19:
Couldn't match expected type ‘HandlerT
App IO (Either a0 GitHub.User)’
with actual type ‘GitHub.Request k0 GitHub.User’
In a stmt of a 'do' block:
possibleUser <- GitHub.userInfoForR "mike-burns"
In the expression:
do { maid <- maybeAuthId;
possibleUser <- GitHub.userInfoForR "mike-burns";
result <- either (("Error: " <>) . tshow) formatUser possibleUser;
defaultLayout
(do { (asWidgetT GHC.Base.. toWidget)
((blaze-markup-0.7.1.1:Text.Blaze.Internal.preEscapedText
GHC.Base.. Data.Text.pack)
"<p>Your current auth ID: ");
(asWidgetT GHC.Base.. toWidget) (toHtml (show maid));
(asWidgetT GHC.Base.. toWidget)
((blaze-markup-0.7.1.1:Text.Blaze.Internal.preEscapedText
GHC.Base.. Data.Text.pack)
"</p>\n");
.... }) }
Handler/Home.hs:44:38:
Couldn't match type ‘Text’ with ‘HandlerT App IO a1’
Expected type: a0 -> HandlerT App IO a1
Actual type: a0 -> Text
In the second argument of ‘(.)’, namely ‘tshow’
In the first argument of ‘either’, namely
‘(("Error: " <>) . tshow)’
Handler/Home.hs:44:45:
Couldn't match type ‘Text’ with ‘HandlerT App IO a1’
Expected type: GitHub.User -> HandlerT App IO a1
Actual type: GitHub.User -> Text
In the second argument of ‘either’, namely ‘formatUser’
In a stmt of a 'do' block:
result <- either (("Error: " <>) . tshow) formatUser possibleUser
The GitHub library seems to be about building requests, and running them. The userInfoForR does such a thing :
userInfoForR :: Name User -> Request k User
Once you have a request, you can run it with one of the following functions, depending if you need to authenticate or not:
executeRequest :: Auth -> Request k a -> IO (Either Error a)
executeRequest' :: Request RO a -> IO (Either Error a)
I don't know about this specific case, but let's say you don't need authentication. So, the following expression would do the trick:
executeRequest' (userInfoForR "mike-burns") :: IO (Either Error User)
Now, in order to use it in a Handler, you'll need to learn about the fact that Handler is an instance of MonadIO, and you can thus do:
euser <- liftIO (executeRequest' (userInfoForR "mike-burns"))
case euser of
Left rr -> ...
Right user -> ...

Persistent selectList type mismatch ‘Database.Persist.Sql.Types.Internal.SqlBackend’

I am working on a Servant 0.7.1 application and trying to use Persistent-2.5 to query a Postgresql database, but I am getting mismatched types with a Persistent query.
This application was previously working with Servant 0.4 and Persistent 2.2, but when I went to Servant 0.7.1 in order to try out the BasicAuth stuff (different stack resolver, which is why I ended up with a higher version of Persistent too), I changed from EitherT ServantErr IO to Servant's Handler monad, and for some reason I could no longer get the Persistent query to compile.
Here's my model definition:
share [mkPersist sqlSettings] [persistLowerCase|
ESeries json
label String
name String Maybe
relatedId ESeriesId Maybe
|]
Based on this blog-post, I have a runDb function that looks like this, which will run inside a ReaderT:
runDb query = do
pool <- asks getPool
liftIO $ runSqlPool query pool
Finally, I have the following api definition and handler:
type ESeriesApi = "series" :> Get '[JSON] [ESeries]
eSeriesApi :: Proxy ESeriesApi
eSeriesApi = Proxy
type AppM = ReaderT Config Handler
readerToHandler :: Config -> AppM :~> Handler
readerToHandler cfg = Nat $ \x -> runReaderT x cfg
eServer :: Config -> Server ESeriesApi
eServer cfg = enter (readerToHandler cfg) eSeriesServer
app :: Config -> Application
app cfg = serve eSeriesApi (eServer cfg)
eSeriesServer :: ServerT ESeriesApi AppM
eSeriesServer = allSeries
allSeries :: AppM [ESeries]
allSeries = do
series <- runDb $ selectList [] []
let results = map (\(Entity _ e) -> e) series
liftIO $ sequence results
I have tried many different variations on this, but it always comes down to the same error:
• Couldn't match type ‘persistent-2.5:Database.Persist.Class.PersistEntity.PersistEntityBackend
(IO ESeries)’
with ‘Database.Persist.Sql.Types.Internal.SqlBackend’
arising from a use of ‘selectList’
• In the second argument of ‘($)’, namely ‘selectList [] []’
In a stmt of a 'do' block: series <- runDb $ selectList [] []
In the expression:
do { series <- runDb $ selectList [] [];
let results = map (\ (Entity _ e) -> ...) series;
liftIO $ sequence results }
It seems that selectList is not returning the right type?
Edit:
I should have mentioned that I am trying to do this with Persistent 2.5 and this code previously worked with earlier versions of Persistent.
It looks like runSqlPool is expecting SqlPersistT or ReaderT SqlBackend but selectList is returning PersistEntityBackend (IO ESeries)

Catching an Exception from runDb

This is a follow-up to my previous post. MaybeT and Transactions in runDb
I thought this will be a simple thing to do but I have been trying to figure this out for over a day and still haven't made much progress. So thought I will give up and ask!
I just added a try function (from Control.Exception.Lifted) to my previous code and I couldn't get the code to type check. Variants like catch and handle had similar issues.
eauth <- LiftIO (
try( runDb $ do
ma <- runMaybeT $ do
valid <- ...
case ma of
Just a -> return a
Nothing -> liftIO $ throwIO MyException
) :: IO (Either MyException Auth)
)
case eauth of
Right auth -> return auth
Left _ -> lift $ left err400 { errBody = "Could not create user"}
My runDb looks like this (I also tried a variant where I removed liftIO):
runDb query = do
pool <- asks getPool
liftIO $ runSqlPool query pool
I get this error:
No instance for (Control.Monad.Reader.Class.MonadReader Config IO)
arising from a use of ‘runDb’
In the expression: runDb
In the first argument of ‘try’, namely
‘(runDb
$ do { ma <- runMaybeT ...
I am running inside servant handler and my return type is AppM Auth where
type AppM = ReaderT Config (EitherT ServantErr IO)
I have tried many combinations of lifting but doesn't seem to be helping. I thought I will take this opportunity to figure out things from scratch and I hit a wall as well. If someone could suggest how you arrived at the answer, it will be super instructive for me.
This has been my thought process:
I see runSqlConn :: MonadBaseControl IO m => SqlPersistT m a -> Connection -> m a
So that seems to imply it will be in the IO monad, which means try should work
I think check the definition of MonadBaseControl which has class MonadBase b m => MonadBaseControl b m | m -> b. At this point I am confused. This functional dependency logic seems to be suggest type m dictates what b will be but in the previous one b was specified as IO.
I check MonadBase and that did not give me any clue either.
I check SqlPersistT and got no clues either.
I reduced the problem to something very simple like result <- liftIO (try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)) and that worked. So I was even more confused at this time. Doesn't runDb work in IO so shouldn't the same thing work for my original code?
I thought I can figure this out by backtracking but it seems like my level of Haskell knowledge is just not sufficient to get at the root of the problem. Appreciate if people can provide step by step pointers as to arrive at the right solution.
Thanks!
General type signature for try:
(MonadBaseControl IO m, Exception e) => m a -> m (Either e a)
Specialized type signature for try (as it appears in your code):
IO Auth -> IO (Either MyException Auth)
So, the monadic value that is the argument to try has type:
IO Auth
Everything listed above, you probably already understood. If we look at the type signature for your runDb, we get this:
runDb :: (MonadReader Config m, MonadIO m) => SqlPersistT m a -> m a
I sort of had to guess because you didn't provide a type signature, but that is probably what it is. So now, the problem should be a little clearer. You are trying to use runDb to create a monadic value for something that's supposed to be in IO. But IO doesn't satisfy the MonadReader Config instance that you need.
To make the mistake more clear, let's make runDb more monomorphic. You could give it this type signature instead:
type AppM = ReaderT Config (EitherT ServantErr IO)
runDb :: SqlPersistT AppM a -> AppM a
And now if you tried to compile your code, you would get an even better error. Instead of telling you
No instance for (Control.Monad.Reader.Class.MonadReader Config IO)
It would tell you that IO doesn't match AppM (although it would probably expand the type synonym). Practically, what this means is that you can't get the shared pool of database connections magically out of IO. You need the ReaderT Config that was passing it around everywhere.
The easiest fix I can think of would be to stop using exceptions where they aren't necessary:
mauth <- runDb $ runMaybeT $ do
... -- Same stuff you were doing earlier
case mauth of
Just auth -> return auth
Nothing -> lift $ left err400 { errBody = "Could not create user"}

Yesod: Type instance error while running `runDB` function in ghci

What is the correct instance to get the runDB return after loading a scaffolded site in ghci? For example, while running this sentence:
runDB $ selectList [UserName ==. "Renny"] []
the error is:
Couldn't match type `PersistMonadBackend
(YesodPersistBackend site0 (HandlerT site0 IO))'
with `persistent-1.3.0.6:Database.Persist.Sql.Types.SqlBackend'
The type variable `site0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Expected type: PersistMonadBackend
(YesodPersistBackend site0 (HandlerT site0 IO))
Actual type: PersistEntityBackend User
In the second argument of `($)', namely
`selectList [UserName ==. "Renny"] []'
In the expression: runDB $ selectList [UserName ==. "Renny"] []
In an equation for `it':
it = runDB $ selectList [UserName ==. "Renny"] []
Thanks in advance
Edit:
I forgot Yesod Scaffold's runDB returns a Handler, which led me to this workaround (though I'm sure it is a better solution):
xs <- runSqlite "MyProject.sqlite3" (selectList [UserName ==. "Renny"] [])
where "MyProject.sqlite3" is the name of Sqlite database.
This is not a generalized solution. According to the documentation and as this post says, it slightly varies for other backends.
The problem is that unlike imperative languages which rely on ambient state, Haskell relies on explicit (and implicit) state passing.
When running runDB $ ... from ghci, you are trying to run this snippet directly within IO, and hence you have no reference to your application state (which includes your database connection). The type error is informing you that it The type variable 'site0' is ambiguous because it cannot infer what application state you are trying to run this statement against.
Within ghci, prefixing runSqlite "MyProject.sqlite3" works because you are specifically setting up the envirionment to run against the correct database, and runSqlite works in IO, which is what ghci wants.

Resources