0

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.

Jivan
  • 21,522
  • 15
  • 80
  • 131
  • 1
    Should the line with `json` be indented further to the right such that it is in the do block of the post endpoint? – Noughtmare Jun 07 '21 at 15:21

1 Answers1

2

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
Noughtmare
  • 9,410
  • 1
  • 12
  • 38
  • Good point for the indent, I fixed the question – Jivan Jun 07 '21 at 15:26
  • Very interesting answer, thanks. Is it the only way though? – Jivan Jun 07 '21 at 15:27
  • 1
    fwiw, `runRedis` does block until the message has been received – Jivan Jun 07 '21 at 15:35
  • I think using a mutable variable in IO like this is the only way. Ideally, the library would have included an alternate version of the pubSub function that can return a value, but that is not the case as far as I can tell. – Noughtmare Jun 07 '21 at 15:45
  • so there's no alternative like using transformer stacks (or anything) to call `json msg` from within the callback? – Jivan Jun 07 '21 at 15:47
  • well actually I don't see how that could work... nevermind, I'll go with mutable variables. thanks a lot. – Jivan Jun 07 '21 at 15:51