Trick to lift IO into any monad

I don’t think it is possible to change liftIOtoM to make m run twice if you bind it with a let like that.
However, I ran:

module Main (main) where

import qualified Control.Monad.Free as Free
import System.IO.Unsafe (unsafePerformIO)

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

liftIOtoM2 :: Monad m => IO a -> m a
liftIOtoM2 io = return $! unsafePerformIO io

main :: IO ()
main = Free.retract $ do
    let m = liftIOtoM (putStrLn "a")
    liftIOtoM (putStrLn "a")
    liftIOtoM (putStrLn "a")
    let n = liftIOtoM2 (putStrLn "b")
    liftIOtoM2 (putStrLn "b")
    liftIOtoM2 (putStrLn "b")
    m
    m
    n
    n

to compare both implementations and I got

a
b
b
b

So you might find my lift useful if you don’t put it in a let.