Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Actually, in Haskell (map print) is pure; it just doesn't do what you expect it to:

    >>> map print ["hello","world","!"]
    <interactive>:2:1:
    No instance for (Show (IO ())) arising from a use of `print'
    Possible fix: add an instance declaration for (Show (IO ()))
    In a stmt of an interactive GHCi command: print it
What's going on here? Let's look at the type of (map print) to find out:

    >>> :t (map print)
    (map print) :: Show a => [a] -> [IO ()]
(map print) is a function which takes a list of values of type a -- such that a can be shown as a String; hence the Show a => constraint -- and returns a list of values of type IO () (pronounced IO unit). These are monadic values representing computations that perform the actual IO. Hence, (map print) is a pure function which carries no side effects.

So, what the heck do we do with this strange list of IO ()s? Well, one answer is to pass them to sequence_:

    >>> sequence_ (map print ["hello","world","!"])
    "hello"
    "world"
    "!"
Ahhh, so now we get to the impure function: sequence_! Actually, sequence_ is a pure function as well. Its type is:

    >>> :t sequence_
    sequence_ :: Monad m => [m a] -> m ()
sequence_ merely takes a list of monadic values and combines them into a single monadic value, discarding any of the elements' return values and returning () instead.

So if everything is a pure function, how do we actually perform the side effects? The simplest way to think of it is that our whole program is a bunch of pure functions which construct a single value representing all of the side effects that will take place over the lifetime of the program. This single value is called main:

    main :: IO ()
    main = sequence_ (map print ["hello","world","!"])
With this idea in mind, we can think of Haskell's runtime as taking this single value main and performing the side effects specified throughout our program.


Actually, every function in Haskell is pure. It's just that some of those pure functions produce values of type (IO a) representing IO actions and, if you sequence those actions into the main action (or a subthread's action), those actions will be performed by the runtime.

So when people say that some functions in Haskell are "impure" they mean that they produce IO actions that, if sequenced, will depend upon or cause IO effects. Thus, both

    map print ["hello","world","!"] :: [IO ()]
and

    mapM_ print ["hello","world","!"] :: IO ()
are equally pure or impure: They both produce actions that have side effects if sequenced. It's just that the first must be sequenced differently than the second since it produces a list of actions and not a singleton action. Since singletons can be sequenced with (>>) and (>>=) you can insert them directly into do notation, which makes many people believe that they are somehow different in terms of purity. (But they are not.)


That's what I said.


I wasn't contradicting you but trying to reinforce the point that there's an equivalence between "impure" functions and pure functions that produce actions (having impure effects if sequenced). In particular, I wanted to highlight that (mapM_ print) is not somehow more impure than (map print). Many people seem to believe that it is.


Indeed. The source code of mapM_ is literally:

   mapM_ f as      =  sequence_ (map f as)




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: