As discussed in numerous other questions, like this, there is never anything you can do with an IO a
value as such – except bind it in another IO
computation, which eventually has to be invoked from either main
or ghci
. And this is not some stupid arbitrary restriction of Haskell, but reflects the fact that something like a file offset can impossibly be known without the program first going “out into the world”, doing the file operation, coming back with the result. In impure languages, this kind of thing just suddenly happens when you try to evaluate an IO “function”, but only because half a century of imperative programming has done it this way doesn't mean it's a good idea. In fact it's a cause for quite a lot of bugs in non-purely functional languages, but more importantly it makes it way harder to understand what some library function will actually do – in Haskell you only need to look at the signature, and when there's no IO
in it, you can be utterly sure1 it won't do any!
Question remains: how do you actually get any “real” work done? Well, it's pretty clever. For beginners, it's probably helpful to keep to this guideline:
- An
IO
action always needs to be evaluated in a do
block.
- To retrieve results from such actions, use the
val <- action
syntax. This can stand anywhere in a do
block except at the end. It is equivalent to what procedural languages write as var val = action()
or similar. If action
had a type IO T
, then val
will have simply type T
!
- The value obtained this way can be used anywhere in the same
do
block below the line you've obtained it from. Quite like in procedural languages.
So if your action was, say,
findOffsetOfFirstChar :: Handle -> Char -> IO FileOffset
you can use it like this:
printOffsetOfQ :: Handle -> IO ()
printOffsetOfQ h = do
offset <- findOffsetOfFirstChar h 'Q'
print offset
Later on you'll learn that many of these do
s arent really necessary, but for the time being it's probably easiest to use them everywhere where there's IO
going on.
1Some people will now object that there is a thing called unsafePerformIO
which allows you to do IO without the signature telling so, but apart from being, well, unsafe, this does not actually belong to the Haskell language but to its foreign function interface.