These days, I hear a lot of "what kind of language makes you learn category theory to do IO" and "monads are a kludge to allow IO in a pure language."
Both of these ideas are rubbish. You can do IO without understanding monads, and monads aren't a way of doing IO. The usual way to demonstrate this is to write yet another monad tutorial. But I'm bored of monad tutorials: half of them don't make sense, and anyway, I just want to do some frickin' IO.
It's pretty simple. Haskell introduces a datatype for IO actions, and a DSL for creating them. The fact that
IOis an instance of the
Monadtypeclass is irrelevant for our purposes. I just want to do some frickin' IO.
The DSL is simple:
dointroduces an IO action. IO actions are formatted either as brace-enclosed, semicolon-delimited (like C) or indented, newline-delimited statements (like Python). Each statement is one of the following:
let n = vbinds
nto the value
n <- aexecutes the action
aand binds the name
nto the result.
a, on its own, executes the action
This is all rather similar to an imperative language, except that two types of assignment are distinguished.
The result of running your newly constructed action is the result of the last action in your
doblock. Which leaves just one more thing you'll need
return. This is not like the return of other languages: it's not a flow control statement.
return xis an IO action that does nothing but yield
Notice that the
let n = vform is actually unneeded: it is equivalent to
n <- return v. So there's really only one type of assignment, if you prefer to look at it that way.
You may be wondering how you pass arguments to an IO action. You don't. You make a function which takes arguments and returns an IO action.
As a short example, I'll write a program that reads in some integers, and outputs a running total.
totalLooptakes the old total, and produces an IO action which reads a line from standard input, converts it to an integer, prints the total, and then runs itself with the new total. It loops forever, so we give it the return type
(), the empty tuple, which is used like
> totalLoop :: Integer -> IO ()
> totalLoop oldTotal =
> input <- getLine -- getLine :: IO String
> let number = read input -- read :: String -> Integer
> let newTotal = oldTotal + number
> print newTotal -- print :: Integer -> IO ()
> totalLoop newTotal
totalLoopwith a zero total.
> main :: IO ()
> main =
> totalLoop 0
And there you have it: an IO-performing (if uninteresting) Haskell program, without any understanding of what monads are.