免费 monad 将 monadic 计算分为数据结构和解释器

例如,涉及从提示读取和写入的命令的计算:

首先,我们将计算的命令描述为 Functor 数据类型

{-# LANGUAGE DeriveFunctor #-}

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

然后我们使用 Free 创建“免费 Monad over TeletypeF”并构建一些基本操作。

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)

由于 Free fMonad,每当 fFunctor 时,我们可以使用标准的 Monad 组合器(包括 do 表示法)来构建 Teletype 计算。

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

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

mockingbird::Teletype a
mockingbird = forever echo

最后,我们写了一个解释器,将 Teletype a 值转化为我们知道如何使用 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

我们可以用来运行Teletype a 中的 Teletype a 计算

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