runAsync :: ((b -> IO ()) -> IO a) -> IO (Async a, b)
runAsync action = do
    -- In case an error is thrown in action', the 'takeMVar' will block
    -- indefinitely. As a result, caller will fail with a very non-helpful
    -- message if we were simply going for `takeMVar mvar`:
    --
    --     "thread blocked indefinitely in an MVar operation"
    --
    -- With this little trick, we make sure not to wait indefinitely on the
    -- MVar. Beside, we shouldn't _ever_ reach the `Left` case below because
    -- 'wait' will re-throw any exception that occurs in the async action.
    -- So, the only way to reach the `Left` case here is to have `handle`
    -- terminating "normally" which can't happen.
    mvar   <- newEmptyMVar
    handle <- async (action (putMVar mvar))
    result <- race (wait handle) (takeMVar mvar)
    case result of
        Left _  -> fail "Action terminated unexpectedly"
        Right b -> return (handle, b)


-- * String manipulation

-- | kebab-case to UPPER_SNAKE_CASE
runAsync :: ((b -> IO ()) -> IO a) -> IO (Async a, b)
runAsync action = do
    -- In case an error is thrown in action', the 'takeMVar' will block
    -- indefinitely. As a result, caller will fail with a very non-helpful
    -- message if we were simply going for `takeMVar mvar`:
    --
    --     "thread blocked indefinitely in an MVar operation"
    --
    -- With this little trick, we make sure not to wait indefinitely on the
    -- MVar. Beside, we shouldn't _ever_ reach the `Left` case below because
    -- 'wait' will re-throw any exception that occurs in the async action.
    -- So, the only way to reach the `Left` case here is to have `handle`
    -- terminating "normally" which can't happen.
    mvar   <- newEmptyMVar
    handle <- async (action (putMVar mvar))
    result <- race (wait handle) (takeMVar mvar)
    case result of
        Left _  -> fail "Action terminated unexpectedly"
        Right b -> return (handle, b)


-- * String manipulation

-- | kebab-case to UPPER_SNAKE_CASE
runAsync action = do
    -- In case an error is thrown in action', the 'takeMVar' will block
    -- indefinitely. As a result, caller will fail with a very non-helpful
    -- message if we were simply going for `takeMVar mvar`:
    --
    --     "thread blocked indefinitely in an MVar operation"
    --
    -- With this little trick, we make sure not to wait indefinitely on the
    -- MVar. Beside, we shouldn't _ever_ reach the `Left` case below because
    -- 'wait' will re-throw any exception that occurs in the async action.
    -- So, the only way to reach the `Left` case here is to have `handle`
    -- terminating "normally" which can't happen.
    mvar   <- newEmptyMVar
    handle <- async (action (putMVar mvar))
    result <- race (wait handle) (takeMVar mvar)
    case result of
        Left _  -> fail "Action terminated unexpectedly"
        Right b -> return (handle, b)


-- * String manipulation

-- | kebab-case to UPPER_SNAKE_CASE