[Haskell-beginners] IO and purity

Ertugrul Söylemez ertesx at gmx.de
Mon Apr 27 01:25:36 UTC 2015


> Can someone please explain how IO operations do not fit in the pure
> category of mathematical function in that they have to be implemented
> via Monads.

Let's not talk about monads at all.  Instead allow me to eliminate a
major misconception right away: I/O does in fact fit into the model of
purity.

The first major insight occurs when you stop thinking about getLine as a
function or an "impure value".  What it really is is just a program.
Every value of type `IO A` is in fact a program that produces a value of
type `A`.

The second major insight happens when you realise that the equal sign in
Haskell introduces an actual equation:

    line = getLine

Here `line` is not a string either.  You have just expressed that `line`
is the exact same thing as `getLine`, so it is a program that, when run,
produces a string.  Now realise that `main` is also a program, the one
that is the actual program you are writing.  When

    putStrLn "Hello!"

is a program that prints a string and results in the dummy value `()`
and you say that

    main = putStrLn "Hello!"

then the program you are writing is that same program, because you have
just defined it to be (remember: equation, not assignment!).

Now you have a bunch of programs.  The final ingredient to writing
side-effecting programs in Haskell is a way to combine those programs.
If `p1` and `p2` are two programs, then `p1 >> p2` is the program that
first executes `p1` and then executes `p2`.  Its result is the result of
`p2`, the latter argument of `(>>)`:

    main = putStrLn "Hello" >> putStrLn "world!"

You have just defined `main` (i.e. your program) to be the program that
first prints the "Hello" line, then prints the "world!" line.  A more
powerful operator is `(>>=)`, which is best explained in terms of an
example:

    putStrLn :: String -> IO ()

Now this is an actual function.  It takes a string and returns a
program, the one that prints that string.  Remember that functions are
the things that can be applied.  Not everything is a function, and in
particular programs are *not* functions, a common and dangerous
misconception.  You could just pass it a string, but what if you want to
print whatever `getLine` resulted in?

    (>>=) :: IO a -> (a -> IO b) -> IO b

Verify that the first argument fits the type of `getLine`, the second
one fits the type of `putStrLn`.  Here is the specialised version, where
`a = String` and `b = ()`:

    (>>=) :: IO String -> (String -> IO ()) -> IO ()

Simply apply it to the desired program (`getLine`) and the desired
program-producing function (`putStrLn`) to get a composite program:

    main = getLine >>= putStrLn

Now let's talk about monads.  It is a nice incident that the `(>>=)`
operator follows a nice algebraic structure (monads), just like addition
follows a nice algebraic structure (monoids).  And it is not the only
operator that follows this structure.  What we can do now is to write
extremely generic functions that work for all monads with a single
implementation.

Welcome to the world of composable polymorphism.  This is the main
reason why Haskell code tends to be much shorter than code in other
languages.  Functions like `mapM` and `forever` are not IO-specific.
They work for all monads, yet they have only a single implementation.

So yes, you are right.  Monads are not at all necessary.  But they are
useful, because we get so much for free.  Basically when you implement a
control functions, chances are that you can actually apply it to a wide
variety of structures, including `IO`.

Exercise:  Can you think of an operation that would work for all
monoids?  If you can, you can implement it *in its general form* in
Haskell.  In other words, once you have established that some type
denotes a monoid, that operation will be available for it for free.
Same thing with monads.


> For e.g. the getLine function has the type IOString and it reads the input
> from the user. Now as I see it the output of getLine will always be same if
> the input remain same (i.e. for input "X" getLine will always return "X" )
> which is the constraint on mathematical functions.
>
> Therefore I don't see why monads are necessary for implementing IO in pure
> languages.
>
> I can understand why Date and Random functions have be implemented via
> monads because their output will always change.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 472 bytes
Desc: not available
URL: <http://mail.haskell.org/pipermail/beginners/attachments/20150427/a3824780/attachment.sig>


More information about the Beginners mailing list