In this answer, I'm using the word 'computation' to describe the effectful, interacting with the environment, part of executing an IO action. Terminology varies.
Given an IO computation act :: IO a
the computation fmap Just act
is a computation which (when run) runs the whole of computation act
and then applies Just to the result.
It doesn't instantly return a thunk. To do that it would have to defer the computation act
into the thunk, and computations can't be put into thunks, only evaluations. Thunks contain pure evaluations only and that's precisely why they work - we know it's OK to defer the evaluation, because we know that it doesn't matter when the evaluation is performed, the answer will still the be same. That's not true of the execution of computations, which can interact with the outside world and may return different answers depending on the environment when it is run.
The primitives (arguably "language-breaking" primitives) unsafePerformIO
and unsafeInterleaveIO
allow you to break the rules in the preceding paragraph and bury an IO computation into a thunk, which is occasionally what you really want and often a terrible, terrible idea.