Access MongoDB from Snap - haskell

I'm trying to access mongo using the mongodb haskell drivers (the snap driver appears to be broken for snap > 0.5) in splice.
This is as far as I've got so far:
testSplice :: Splice AppHandler
testSplice = do
record <- liftIO $ do
pipe <- runIOE $ connect (host "127.0.0.1")
results <- access pipe master "db" (find $ select [] "coll")
close pipe
rest result
return $ [TextNode $ T.pack $ show $ records]
I understand that I need to use liftIO there, as the mongo action occurs inside an IO monad, and I want to pull that back out. Where my understanding breaks down is the result of compiling that splice:
Couldn't match expected type `IO a0'
with actual type `Action m0 [Database.MongoDB.Document]'
I'm sorry to post a "Send me the codes plz" question, but I'm at loss: where am I going wrong, and how do I make this work?

Here is your function annotated with type signatures. I think this makes it
pretty clear where the problem lies.
testSplice :: Splice AppHandler
testSplice = do
record <- liftIO $ do
pipe <- runIOE $ connect (host "127.0.0.1") -- :: IO Pipe
results <- access pipe master "db" (find $ select [] "coll")
-- ^ :: IO (Either Failure Cursor)
close pipe -- :: IO ()
rest result -- :: Action m [Document]
return $ [TextNode $ T.pack $ show $ records]
Everything inside the "liftIO $ do" block must be an IO action. The last line
"rest result" is not. One solution is to prepend that line with 'access pipe
master "db"' just like you've done with find. Another solution is to avoid
calling "access pipe..." twice and replace the find line with the following:
result <- access pipe master "db" (find (select [] "coll") >>= rest)
Then replace the "rest result" line with "return result"
What Daniel says about the find line not needing liftIO is correct, but in
this case it doesn't matter because IO has a MonadIO instance. So it's probably just as easy to keep all the liftIO stuff in one block.

I am not a MongoDB expert, so I'm not 100% sure (and I can't test it), but I suspect that you've got your liftIO in the wrong place. We have liftIO :: MonadIO m => IO a -> m a, so we should apply liftIO to actions that are actually IO, but which we want to be something bigger than IO. I suspect that access is a function with a bigger-than-IO return type. Assuming runIOE, close, and rest all actually have IO return types, we'd then do something like this:
testSplice = do
pipe <- liftIO . runIOE $ connect (host "127.0.0.1")
results <- access pipe master "db" (find $ select [] "coll") -- note: no liftIO on this one because it's presumably already lifted
liftIO $ close pipe
record <- liftIO $ rest result
return [TextNode . T.pack . show $ records]
If some of those actions actually are not IO things, then you can drop the liftIO from those ones.
As you observed, this can be cleaned up a bit: any adjacent lines that start with liftIO can be coalesced. So if the above turns out to be the right places for liftIOs, then it could also be written as:
testSplice = do
pipe <- liftIO . runIOE $ connect (host "127.0.0.1")
results <- access pipe master "db" (find $ select [] "coll")
liftIO $ do
close pipe
record <- rest result
return [TextNode . T.pack . show $ records]
(The last one there is OK because return = liftIO . return for any sane implementation of liftIO.)

Related

Cannot display IO [[String]] in app created in Spock and Lucid

I have an issue with Spock, Lucid and IO in Haskell. I've got a function
f :: MySQLConn -> IO [[String]]
Output of f is result of statement "SELECT * FROM TABLE" with converted [MySQLValue] lists to [String] lists.
Then I want to use function f in Spock to display output in my app. I tried to use something like:
(...)
get "history" $ lucid $ do
let offers = c >>= f
mapM (mapM (p_ . toHtml) ) offers
(...)
Where c is used to create a connection with DB.
But it doesn't work. An error is: "No instance for (Traversable IO) arising from a use of ‘mapM’".
If I try:
get "history" $ lucid $ do
mapM (mapM (p_ . toHtml) ) [["a", "b"], ["1","x"]]
it is correct. So my question is - how can I use the result of f in this app?
I think you can perform the IO action inbetween get and lucid:
get "history" $ do
offers <- liftIO $ c >>= f
lucid $ mapM (mapM (p_ . toHtml) ) offers
With liftIO from Control.Monad.IO.Class.

Handling exceptions (ExceptT) in chain of actions

I am trying to use an exception to skip parts of the code here. Instead of getting caught by catcheE and resuming normal behavior all following actions in the mapM_ chain get skipped.
I looked at this question and it appears that catchE ~ main and checkMaybe ~ intercept.
I also checked the implementation of mapM_to be sure it does what i want it to, but i don't understand how the Left value can escape dlAsset to affect the behavior of mapM_.
I refactored this from a version where i simply used an empty string as an exception marker for the failed lookup. In that version checkMaybe just returned a Right value immediately and it worked (matching on "" to 'catch')
import Data.HashMap.Strict as HM hiding (map)
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString.Char8 as BSC8
import qualified JSONParser as P -- my module
retrieveAssets :: (Text -> Text) -> ExceptT Text IO ()
retrieveAssets withName = withManager $ (lift ((HM.keys . P.assets)
<$> P.raw) ) >>= mapM_ f
where
f = \x -> dlAsset x "0.1246" (withName x)
dlAsset :: Text -> Text -> Text -> ReaderT Manager (ExceptT Text IO) ()
dlAsset name size dest = do
req <- lift $ (P.assetLookup name size <$> P.raw) >>= checkMaybe
name >>= parseUrl . unpack -- lookup of a url
res <- httpLbs req
lift $ (liftIO $ BS.writeFile (unpack dest) $ responseBody res)
`catchE` (\_ -> return ()) -- always a Right value?
where
checkMaybe name a = case a of
Nothing -> ExceptT $ fmap Left $ do
BSC8.appendFile "./resources/images/missingFiles.txt" $
BSC8.pack $ (unpack name) ++ "\n"
putStrLn $ "lookup of " ++ (unpack name) ++ " failed"
return name
Just x -> lift $ pure x
(had to reformat to become somewhat readable here)
edit: i'd like to understand what actually happens here, that would probably help me more than knowing which part of the code is wrong.
The problem is that your call to catchE only covered the very last line of dlAsset. It needs to be moved to the left of the do-notation indentation level to cover all of the do notation.

MaybeT and Transactions in runDb

For my previous question on chaining failures, Michael Snoyman had suggested I use MaybeT to run them so if any of them fails, it will just short-circuit to Nothing.
I was under the impression runDb runs everything in a transaction. So shouldn't a failure at any point in code automatically rollback the transaction?
mauth <- runDb $ runMaybeT $ do
valid <- MaybeT $ return $ listToMaybe errs
uid <- MaybeT $ insertUnique u
vid <- MaybeT $ getBy $ UniqueField v -- this step fails but previous insert does not roll back
auth <- liftIO $ createAuthToken uid
return auth
When I run the above code, the getBy fails but user was still inserted. Am I misunderstanding that runDb will rollback on a Nothing inside MaybeT? Do I need to use some other Monad for this to work?
Appreciate your thoughts on how to best rollback on failure.
Update:
This is what I ended up doing per Michael's suggestion.
mauth <- runDb $ do
ma <- runMaybeT $ do
valid <- ...
case ma of
Just _ -> return ma
Nothing -> liftIO $ throwIO MyException
Now I need to figure out how to catch this exception nicely outside and return a proper error message back.
Thanks!
Returning Nothing is not the same thing as a failure. You'd need to throw a runtime exception (via something like throwIO) for Persistent to treat it as a rollback situation.

What's an idiomatic way of handling a lazy input channel in Haskell

I am implementing an IRC bot and since I am connecting over SSL by using OpenSSL.Session I use lazyRead function to read data from the socket. During the initial phase of the connection I need to perform several things in order: nick negotiation, nickserv identification, joining channels etc) so there is some state involved. Right now I came up with the following:
data ConnectionState = Initial | NickIdentification | Connected
listen :: SSL.SSL -> IO ()
listen ssl = do
lines <- BL.lines `fmap` SSL.lazyRead ssl
evalStateT (mapM_ (processLine ssl) lines) Initial
processLine :: SSL.SSL -> BL.ByteString -> StateT ConnectionState IO ()
processLine ssl line = do case message of
Just a -> processMessage ssl a
Nothing -> return ()
where message = IRC.decode $ BL.toStrict line
processMessage :: SSL.SSL -> IRC.Message -> StateT ConnectionState IO ()
processMessage ssl m = do
state <- S.get
case state of
Initial -> when (IRC.msg_command m == "376") $ do
liftIO $ putStrLn "connected!"
liftIO $ privmsg ssl "NickServ" ("identify " ++ nick_password)
S.put NickIdentification
NickIdentification -> do
when (identified m) $ do
liftIO $ putStrLn "identified!"
liftIO $ joinChannel ssl chan
S.put Connected
Connected -> return ()
liftIO $ print m
when (IRC.msg_command m == "PING") $ (liftIO . pong . mconcat . map show) (IRC.msg_params m)
So when I get to the "Connected" state I still end up going through the case statement even though it's only really needed to initialize the connection. The other problem is that adding nested StateT's would be very painful.
Other way would be to replace mapM with something custom to only process lines until we are connected and then start another loop over the rest. This would require either keeping track of what's left in the list or invoking SSL.lazyRead once again (which is not too bad).
Another solution is to keep the remaining lines list in the state and draw lines when needed similar to getLine.
What's the better thing to do in this case? Would Haskell's laziness make it so that we go directly to Connected case after state stops updating or is case always strict?
You can use the Pipe type from pipes. The trick is that instead of creating a state machine and a transition function you can encode the the state implicitly in the control flow of the Pipe.
Here is what the Pipe would look like:
stateful :: Pipe ByteString ByteString IO r
stateful = do
msg <- await
if (IRC.msg_command msg == "376")
then do
liftIO $ putStrLn "connected!"
liftIO $ privmsg ssl "NickServ" ("identify " ++ nick_password)
yield msg
nick
else stateful
nick :: Pipe ByteString ByteString IO r
nick = do
msg <- await
if identified msg
then do
liftIO $ putStrLn "identified!"
liftIO $ joinChannel ssl chan
yield msg
cat -- Forward the remaining input to output indefinitely
else nick
The stateful pipe corresponds to the stateful part of your processMessage function. It handles initialization and authentication, but defers further message processing to downstream stages by re-yielding the msg.
You can then loop over every message this Pipe yields by using for:
processMessage :: Consumer ByteString IO r
processMessage = for stateful $ \msg -> do
liftIO $ print m
when (IRC.msg_command m == "PING") $ (liftIO . pong . mconcat . map show) (IRC.msg_params m)
Now all you need is a source of ByteString lines to feed to processMessage. You can use the following Producer:
lines :: Producer ByteString IO ()
lines = do
bs <- liftIO (ByteString.getLine)
if ByteString.null bs
then return ()
else do
yield bs
lines
Then you can connect lines to processMessage and run them:
runEffect (lines >-> processMessage) :: IO ()
Note that the lines Producer does not use lazy IO. It will work even if you use the strict ByteString module, but the behavior of the entire program will still be lazy.
If you want to learn more about how pipes works, you can read the pipes tutorial.

Using mapM f [list] where f is defined with do notation

I currently have this code which will perform the main' function on each of the filenames in the list files.
Ideally I have been trying to combine main and main' but I haven't made much progress. Is there a better way to simplify this or will I need to keep them separate?
{- Start here -}
main :: IO [()]
main = do
files <- getArgs
mapM main' files
{- Main's helper function -}
main' :: FilePath -> IO ()
main' file = do
contents <- readFile file
case (runParser parser 0 file $ lexer contents) of Left err -> print err
Right xs -> putStr xs
Thanks!
Edit: As most of you are suggesting; I was trying a lambda abstraction for this but wasn't getting it right. - Should've specified this above. With the examples I see this better.
The Control.Monad library defines the function forM which is mapM is reverse arguments. That makes it easier to use in your situation, i.e.
main :: IO ()
main = do
files <- getArgs
forM_ files $ \file -> do
contents <- readFile file
case (runParser f 0 file $ lexer contents) of
Left err -> print err
Right xs -> putStr xs
The version with the underscore at the end of the name is used when you are not interested in the resulting list (like in this case), so main can simply have the type IO (). (mapM has a similar variant called mapM_).
You can use forM, which equals flip mapM, i.e. mapM with its arguments flipped, like this:
forM_ files $ \file -> do
contents <- readFile file
...
Also notice that I used forM_ instead of forM. This is more efficient when you are not interested in the result of the computation.

Resources