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
IO
is an instance of the Monad
typeclass is irrelevant for our purposes. I just want to do some frickin' IO.The DSL is simple:
do
introduces 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 = v
bindsn
to the valuev
.n <- a
executes the actiona
and binds the namen
to the result.a
, on its own, executes the actiona
.
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
do
block. 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 x
is an IO action that does nothing but yield x
when executed.Notice that the
let n = v
form 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.
totalLoop
takes 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 void
in Haskell.
> totalLoop :: Integer -> IO ()
> totalLoop oldTotal =
> do
> input <- getLine -- getLine :: IO String
> let number = read input -- read :: String -> Integer
> let newTotal = oldTotal + number
> print newTotal -- print :: Integer -> IO ()
> totalLoop newTotal
main
simply runs totalLoop
with a zero total.
> main :: IO ()
> main =
> do
> totalLoop 0
And there you have it: an IO-performing (if uninteresting) Haskell program, without any understanding of what monads are.
No comments:
Post a Comment