Deep Shah
Deep Shah

Reputation: 440

In Yesod, can we invoke a method that returns Handler () from a method that returns only IO ()

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?


UPDATE

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?


UPDATE 2

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.

What I ended up doing was

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

Answers (2)

wiz
wiz

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

bheklilr
bheklilr

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

Related Questions