Free play, part one

When I read John A De Goes post A Modern Architecture for FP I found it to be a bit too vague for me, but the topic was just interesting enough to finally push me to play a little with free monads. It’s not the first post I’ve read on the topic, there have been many before. None have quite managed to push me into actually doing something though!

A file API

To make it concrete but still short enough to not bore readers I came up with a small API for working with files:

data SimpleFileF a =
  LoadFile FilePath (String -> a)
  | SaveFile FilePath String a

The free monad wraps a functor, so here’s an implementation of that

instance Functor SimpleFileF where
  fmap f (LoadFile fp f')= LoadFile fp (f . f')
  fmap f (SaveFile fp d a) = SaveFile fp d (f a)

Now for some convenient functions to work with the API type

loadFile :: FilePath -> Free SimpleFileF String
loadFile fp = liftF $ LoadFile fp id

saveFile :: FilePath -> String -> Free SimpleFileF ()
saveFile fp d = liftF $ SaveFile fp d ()

With this in place I can write a somewhat more complex one

withSimpleFile :: (String -> String) -> FilePath -> Free SimpleFileF ()
withSimpleFile f fp = do
  d <- loadFile fp
  let result = f d
  saveFile (fp ++ "_new") result

Now I need a way to run programs using the API

runSimpleFile :: Free SimpleFileF a -> IO a
runSimpleFile = foldFree f
    f (LoadFile fp f') = liftM f' $ readFile fp
    f (SaveFile fp d r) = writeFile fp d >> return r

If this code was save in the file FreePlay.hs I can now convert it all to upper case by using

runSimpleFile $ withSimpleFile toUpper "FreePlay.hs"

which of course will create the file FreePlay.hs_new.

What have I bought so far?

Well, not much really.

So far it’s not much more than a rather roundabout way to limit what IO actions are available. In other words it’s not much more than what can be done by creating a limited IO, as in my earlier posts here and here.

Of course it would be possible to write another run function, e.g. one that doesn’t actually perform the file actions but just says what it would do. The nice thing though is, to use that same metaphor as John does, that I can add a layer to the onion. In other words, I can decorate each use of my SimpleFileF API with some other API. I think it was John’s description of this layering that pushed me to play with Free.

⟸ Trick for pre-processing source in CMake Free play, part two ⟹
Leave a comment