trace
is the easiest to use method for debugging. It’s not in IO
exactly for the reason you pointed: no need to lift your code in the IO
monad. It’s implemented like this
trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
putTraceMsg string
return expr
So there is IO behind the scenes but unsafePerformIO
is used to escape out of it. That’s a function which potentially breaks referential transparency which you can guess looking at its type IO a -> a
and also its name.