Haskell Language Free monads split monadic computations into data structures and interpreters


For instance, a computation involving commands to read and write from the prompt:

First we describe the "commands" of our computation as a Functor data type

{-# LANGUAGE DeriveFunctor #-}

data TeletypeF next
    = PrintLine String next
    | ReadLine (String -> next)
    deriving Functor

Then we use Free to create the "Free Monad over TeletypeF" and build some basic operations.

import Control.Monad.Free (Free, liftF, iterM)

type Teletype = Free TeletypeF

printLine :: String -> Teletype ()
printLine str = liftF (PrintLine str ())

readLine :: Teletype String
readLine = liftF (ReadLine id)

Since Free f is a Monad whenever f is a Functor, we can use the standard Monad combinators (including do notation) to build Teletype computations.

import Control.Monad -- we can use the standard combinators

echo :: Teletype ()
echo = readLine >>= printLine

mockingbird :: Teletype a
mockingbird = forever echo

Finally, we write an "interpreter" turning Teletype a values into something we know how to work with like IO a

interpretTeletype :: Teletype a -> IO a
interpretTeletype = foldFree run where
  run :: TeletypeF a -> IO a
  run (PrintLine str x) = putStrLn *> return x
  run (ReadLine f) = fmap f getLine

Which we can use to "run" the Teletype a computation in IO

> interpretTeletype mockingbird
this will go on forever
this will go on forever