Terminal IO example in Haskell

Last post we looked at using a less general form of the Free monad to purely represent side-effects in F#. Because Haskell supports higher-order polymorphism it makes using this approach much easier. Here is the complete example from that post, rewritten in Haskell:

{-# LANGUAGE DeriveFunctor #-}
import Control.Monad.Free

data Terminal a =
    WriteLine String a
    | ReadLine (String -> a)
    deriving (Functor)

writeLine :: String -> Free Terminal ()
writeLine s = liftF $ WriteLine s ()
readLine :: Free Terminal String
readLine = liftF $ ReadLine id

helloWorld :: Free Terminal ()
helloWorld = do
    writeLine "Hi, what's your name?"
    name <- readLine
    writeLine $ "Hello " ++ name

interpretIO :: Free Terminal a -> IO a
interpretIO (Free (WriteLine s a)) = putStrLn s >> interpretIO a
interpretIO (Free (ReadLine f)) = getLine >>= interpretIO . f
interpretIO (Pure a) = return a

This is using Free and liftF from Control.Monad.Free, but for the sake of equivalence with the F# example, here’s a definition that can be used in place of the import Control.Monad.Free line in the above code.

data Free f a = Pure a | Free (f (Free f a))
instance Functor f => Monad (Free f) where
    return = Pure
    (Pure a) >>= f = f a
    (Free fr) >>= f = Free (fmap (>>= f) fr)

liftF :: Functor f => f a -> Free f a
liftF =  Free . fmap Pure