Reputation: 440
Please forgive my ignorance but, is there is way to invoke a method that returns Handler ()
from a method that returns only IO ()
For example consider these two methods
onReceiveMessage :: IO ()
onReceiveMessage = do
_ <- putStrLn "Received Message"
_ <- doSomething
return ()
doSomething :: Handler ()
doSomething = return ()
This does not seem to compile. I tried to compile it in a few different ways but all in vein. I am sure it must be possible but just don't know how.
Any Idea?
Expanding on the previous example, lets say I have another function that takes in a value of the previous function and returns IO (). That also does not work.
onReceiveMessage :: IO ()
onReceiveMessage =
doSomething >>= doSomethingElse
doSomething :: Handler MessageStatus
doSomething = return MessageStatus.Success
doSomethingElse :: MessageStatus -> IO ()
doSomethingElse _ = return ()
This also does not seem to work. To invoke an IO action from Handler is possible using the liftIO function for e.g. the below function compiles and works fine. It invokes a IO () action from a function that returns Handler MessageStatus. This is achieved using the liftIO function.
doSomething' :: Handler MessageStatus
doSomething' = (liftIO $ putStrLn "hello world") >> return MessageStatus.Success
Do we have something similar to invoke Handler action from IO?
Giving more context and explaining how I solved the problem.
I was trying to listen to RabbitMQ using the amqp package in an Yesod application.
(Message, Envelope) -> IO ()
. runDB
.Handler
I was not able to invoke it as is from the message callback.What I ended up doing was
App
(Foundation
) object while its being built in the Application.hs
and passed it to my code.Overall I feel I might have over complicated things but currently I do not know of any better way of doing this. If anyone has a better idea, I would love to hear it.
Upvotes: 2
Views: 648
Reputation: 500
In recent versions you can use Yesod.Core.Handler.handlerToIO
to get back into your cozy Handler stack.
But i recommend decoupling messaging and processing:
getOneMsg :: Channel -> Text -> Handler (Message, Envelope)
getOneMsg chan queue = liftIO $ do
mbox <- newEmptyMVar
tag <- consumeMsgs chan queue NoAck (putMVar mbox)
reply <- takeMVar mbox
cancelConsumer chan tag
return reply
myHandler = do
... regular handler code ...
(msg, env) <- getOneMsg chan queue
... regular handler code ...
Upvotes: 1
Reputation: 54068
First of all, your indentions look off. White space in Haskell is important. But more importantly, every statement in a single monadic action has to be within the same monad:
onReceiveMessage :: IO ()
onReceiveMessage = do
putStrLn "Received Message" :: IO ()
doSomething :: Handler () -- Illegal! Must be type IO ()
return () :: IO ()
doSomething :: Handler ()
doSomething = return ()
So no, you can not return a handler from an IO
action.
UPDATE
The Handler
type is actually an alias for a more complex type which you can think of as "wrapping" the IO monad. What it allows you to do is "lift" an IO action into the Handler
monad, but this only goes one way. In order to convert a Handler
action into an IO
action, there is usually a function provided by the library that is something like runHandler :: Handler [-> other args] -> IO ()
. I'm not particularly familiar with Yesod, but the pattern is similar across many libraries. This function is used to convert an entire Handler
action into an IO
action, and is usually reserved for running the server itself.
The complicated answer is that the Handler
type is what is known as a monad transformer. They can be pretty tricky to learn at first, but you can just think of it as a monad that contains another monad. Monads can not perform operations outside themselves, so IO
can't perform Handler
actions, but since Handler
contains IO
inside it, an IO
action can be "lifted" up a level. The really useful thing about monad transformers is that they can be layered indefinitely, and it essentially lets you compose the behavior of different monads together. For instance, imagine if you had a Maybe
action, but you also wanted State
functionality, Writer
functionality, and IO
functionality. With monad transformers, this becomes possible, if a bit more complex. However, the order in which these are composed can often matter, since operations can be lifted, but not lowered.
Upvotes: 1