Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the meaning of IO actions within pure functions?

Tags:

haskell

monads

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?

like image 424
Riccardo T. Avatar asked Mar 22 '12 09:03

Riccardo T.


People also ask

What is an IO action?

IO actions are used to affect the world outside of the program. Actions take no arguments but have a result value. Actions are inert until run. Only one IO action in a Haskell program is run ( main ).

What does IO mean in Haskell?

IO is the way how Haskell differentiates between code that is referentially transparent and code that is not. IO a is the type of an IO action that returns an a . You can think of an IO action as a piece of code with some effect on the real world that waits to get executed.

How is Haskell IO pure?

Haskell is a pure language Being pure means that the result of any function call is fully determined by its arguments. Procedural entities like rand() or getchar() in C, which return different results on each call, are simply impossible to write in Haskell.

Is IO A impure?

The presence of the IO type constructor means a function is impure, just as the absence of the const keyword in C/C++ means data might be modified. Loosely speaking, within a do block, the output of one line becomes the input of the next.


1 Answers

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 value Just (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 executing main, 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 IO actions inside Maybe; imagine a function String -> 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 of putStrLn "???".

like image 79
ehird Avatar answered Oct 14 '22 13:10

ehird