I thought that in principle Haskell’s type system would forbid calls to impure functions (i.e. f :: a -> IO b) from pure ones, but today I realized that by calling them with return they compile just fine. In this example:
h :: Maybe ()
h = do
return $ putStrLn "???"
return ()
h works in the Maybe monad, but it’s a pure function nevertheless. Compiling and running it simply returns Just () as one would expect, without actually doing any I/O. I think Haskell’s laziness puts the things together (i.e. putStrLn‘s return value is not used – and can’t since its value constructors are hidden and I can’t pattern match against it), but why is this code legal? Are there any other reasons that makes this allowed?
As a bonus, related question: in general, is it possible to forbid at all the execution of actions of a monad from within other ones, and how?
IO actions are first-class values like any other; that’s what makes Haskell’s IO so expressive, allowing you to build higher-order control structures (like
mapM_) from scratch. Laziness isn’t relevant here,1 it’s just that you’re not actually executing the action. You’re just constructing the valueJust (putStrLn "???"), then throwing it away.putStrLn "???"existing doesn’t cause a line to be printed to the screen. By itself,putStrLn "???"is just a description of some IO that could be done to cause a line to be printed to the screen. The only execution that happens is executingmain, which you constructed from other IO actions, or whatever actions you type into GHCi. For more information, see the introduction to IO.Indeed, it’s perfectly conceivable that you might want to juggle about
IOactions insideMaybe; imagine a functionString -> Maybe (IO ()), which checks the string for validity, and if it’s valid, returns an IO action to print some information derived from the string. This is possible precisely because of Haskell’s first-class IO actions.But a monad has no ability to execute the actions of another monad unless you give it that ability.
1 Indeed,
h = putStrLn "???" `seq` return ()doesn’t cause any IO to be performed either, even though it forces the evaluation ofputStrLn "???".