The problem is observable effects:
-
ST
actions likereadSTRef
andwriteSTRef
only manipulate the contents of the program’s memory - usually only made visible with a debugger. -
however,
IO
actions likegetChar
andputChar
work with I/O devices which can potentially manipulate anything, including the program itself.
As for safety with concurrency, everyone’s favourite “type-smith” has already shown how unsafeInterleaveST
(and consequently unsafeInterleaveIO
) breaks regular Haskell semantics, even though their results are monadic:
unsafeInterleaveST :: ST s a -> ST s a
unsafeInterleaveIO :: IO a -> IO a
as opposed to:
unsafePerformST :: ST s a -> a
unsafePerformIO :: IO a -> a
If only there was a denotative way to write system software…