Search code examples
haskellredispublish-subscribescotty

Use Redis.Message from outside of the pubSub callback


In the documentation for Hedis, an example of using the pubSub function is given:

pubSub :: PubSub -> (Message -> IO PubSub) -> Redis ()

pubSub (subscribe ["chat"]) $ \msg -> do
    putStrLn $ "Message from " ++ show (msgChannel msg)
    return $ unsubscribe ["chat"]

Given that pubSub returns a Redis (), is it nevertheless possible to re-use this msg message further down in the code, from outside the callback?

I'm calling pubSub from a Scotty endpoint which runs in the ScottyM monad, and should return (to keep a long story short) a json msg

myEndpoint :: ScottyM ()
myEndpoint =
    post "/hello/world" $ do
        data :: MyData <- jsonData
        runRedis redisConn $ do
            pubSub (subscribe ["channel"]) $ \msg -> do
                doSomethingWith msg
                return $ unsubscribe ["channel"]

        -- how is it possible to retrieve `msg` from here?
        json $ somethingBuiltFromMsg

Alternatively, is there a way to use Scotty's json from within the callback? I haven't been able to do this so far.


Solution

  • I will assume you meant to indent the line with json further.

    You can use mutable variables in IO for that, e.g. IORef:

    import Data.IORef (newIORef, writeIORef, readIORef)
    import Control.Monad.IO.Class (liftIO)
    
    myEndpoint :: ScottyM ()
    myEndpoint =
        post "/hello/world" $ do
            data :: MyData <- jsonData
            msgRef <- liftIO (newIORef Nothing)
            runRedis redisConn $ do
                pubSub (subscribe ["channel"]) $ \msg -> do
                    writeIORef msgRef (Just msg)
                    return $ unsubscribe ["channel"]
            Just msg <- liftIO (readIORef msgRef)
            json $ doSomethingWithMsg msg
    

    Edit: I guess I don't really know if the runRedis function blocks until the message has been received, if that is not the case then you can use an MVar instead:

    import Control.Concurrent.MVar (putMVar, takeMVar, newEmptyMVar)
    import Control.Monad.IO.Class (liftIO)
    
    myEndpoint :: ScottyM ()
    myEndpoint =
        post "/hello/world" $ do
            data :: MyData <- jsonData
            msgVar <- liftIO newEmptyMVar
            runRedis redisConn $ do
                pubSub (subscribe ["channel"]) $ \msg -> do
                    putMVar msgVar msg
                    return $ unsubscribe ["channel"]
            msg <- liftIO (takeMVar msgVar)
            json $ doSomethingWithMsg msg