Merely evaluating some IO
action doesn’t do anything at all. You can think of IO
sort of like a really big sum type of all the possible side-effectful things Haskell can do, even if it isn’t actually implemented like that at all. Something like this:
data IO a where
PutStrLn :: String -> IO ()
ReadFile :: FilePath -> IO String
ExitWith :: ExitCode -> IO a
...
One of the IO
constructors in this theoretical visualization would be a Sequence
constructor, with a type signature like this:
Sequence :: IO a -> (a -> IO b) -> IO b
This constructor is used to implement >>=
for the IO
type.
Inside of GHC is a magical function called magicallyExecuteIO
with type IO a -> a
, which coordinates for each action to actually perform its corresponding side-effect. (Incidentally, this function is also sometimes pronounced unsafePerformIO
.) GHC implicitly calls magicallyExecuteIO
on the result of your program’s main
function, as well as on expressions written in GHCi.
However, without using magicallyExecuteIO
, evaluating one of the IO
constructors like PutStrLn
doesn’t do anything. In this implementation, it would just work like any other data constructor:
ghci> Just (PutStrLn "hello!")
Just (PutStrLn "hello!") :: Maybe (IO ())
(I’ve wrapped it with Just
to prevent GHCi from running the IO
action.)
Of course, GHC’s actual IO
type isn’t implemented this way, but that’s really just an implementation detail. Evaluating an IO a
value doesn’t cause a side-effect to happen any more than evaluating a Maybe a
value does. Only magicallyExecuteIO
can do that.
sillyIO
is defined nowhere.sillyDebug = (trace (show x) False) `seq` (x + y)
doesn't even compile. And describe the problem you're facing. What did you expect? What do you get? – Fereseq
.a `seq` b
doesn’t exactly force evaluation ofa
. It generates a dependency between thunks, so ifb
is evaluated, thena
is too.print (snd (x `seq` y, z))
never evaluatesx
because it never evaluatesy
. – Tennes