GHCi always runs statements in IO. I wanted to step into StateT, ReaderT or an app monad, work there for a while, and step back out. So I made a small proof of concept.
:e runner takes any runner :: n () -> m (), infers n with a typed hole, and opens a nested REPL inside n. :q steps back out.
Code: haskell-playground/exe/playground.hs at repl · bglgwyng/haskell-playground · GitHub
Related: GHC #9966 proposes the same idea for GHCi.