Dear Haskellers,
I recently concocted a definition of a lifting function for IO with a type signature like
liftIOtoM :: Monad m => IO a -> m a
This is very similar to liftIO, but it depends on a Monad m
constraint rather than MonadIO m
. I share the implementation below, of which I wish to ask the community for assessment. But first let me go into a bit of motivation.
Depending on Monad m
makes it possible to implement a version of traceM that doesn’t have the caveats about messages being printed less often than desired.
traceM = liftIOtoM . hPutStrLn stderr
Similarly, it also allows me to define measureM in timestats for any monad instead of only for MonadIO m
.
Here’s the implementation.
liftIOtoM :: Monad m => IO a -> m a
liftIOtoM m = do
-- The fictitious state is only used to force @unsafePerformIO@
-- to run @m@ every time @liftIOtoM m@ is evaluated.
s <- getStateM
let p = unsafePerformIO $ do
r <- m
pure (s, r)
case p of
(_, r) -> pure r
where
-- We mark this function as NOINLINE to ensure the compiler cannot reason
-- by unfolding that two calls of @getStateM@ yield the same value.
{-# NOINLINE getStateM #-}
getStateM = pure True
As you can see, it depends on unsafePerformIO
which requires special attention to not introduce unwanted behavior. I have spotted a couple of mistakes in my initial attempts, which I have fixed for this presentation. So here is the question: do you think this code could do ok for the above use cases?
Thanks!
Facundo