Applicative IO

Introduction

Haskell famously has ‘monadic IO’, which allows us to compose IO actions with bind. For example, here’s a crude version of cp:1

cp ifn ofn = readFile ifn >>= writeFile ofn

In practice we would normally use do-notation:

cp ifn ofn = do
txt <- readFile ifn
writeFile ofn txt

Unless of course we’re working in ghci, where one-line expressions seem much more convenient to me—perhaps because I wrote a lot of Perl in the past.

Messing around in ghci

In the example above, bind cleanly chains the IO actions. As a reminder it has type:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

Sometimes though we don’t want to put the data into another IO action, but rather analyse it, hopefully with a pure function. Bind looks less helpful here because our results will typically not be in IO i.e. we have (a -> b) not (a -> m b).

Staying with bind for now though, we’ll need a way to make an IO action to display our results. Enter print:

ghci is happy to print this IO Int. I think this is better than the bind approaches, but it’s still quite noisy.

Applicative IO

Now, where there’s a monad, there’s also an applicative,3 and it struck me the other day this this applies to IO. So, we can actually write wc as:

ghci> length . words <$> readFile "/usr/share/dict/words"
235886

which strikes me as nice and simple.

We can’t use applicative for all the IO tasks in ghci: we will need the full power of the monad to collapse the IO (IO a)) we get from chaining IO actions directly. For example, the result above is in IO, so even if we convert it into a String we can't chain it to writeFile:

Functor IO

For the simple case of one argument, we could also replace the applicative <$> with fmap:

ghci> fmap (length . words) $ readFile "/usr/share/dict/words"
235886

but I think that’s less clear.

Conclusions

I think it’s often pretty useful and productive to explore code from the ghci prompt. However, things got messy if part of the exploration needed data from files, which discouraged me from doing the right thing.

I think using IO’s applicative instance solves a lot of the problems, and am somewhat annoyed I didn’t think of it before.