foldFree 和 iterM 如何工作

有一些函数可以帮助拆除 Free 计算,将它们解释为另一个 monad miterM :: (Functor f, Monad m) => (f (m a) -> m a) -> (Free f a -> m a)foldFree::Monad m => (forall x. f x -> m x) -> (Free f a -> m a)。他们在做什么?

首先让我们看看如何手动将 Teletype a 函数解释为 IO。我们可以看到 Free f a 被定义了

data Free f a 
    = Pure a 
    | Free (f (Free f a))

Pure 案例很简单:

interpretTeletype::Teletype a -> IO a
interpretTeletype (Pure x) = return x
interpretTeletype (Free teletypeF) = _

现在,如何解释用 Free 构造函数构建的 Teletype 计算?我们想通过检查 teletypeF::TeletypeF (Teletype a) 来得到 IO a 的值。首先,我们将编写一个函数 runIO::TeletypeF a -> IO a,它将单个自由 monad 层映射到 IO 动作:

runIO::TeletypeF a -> IO a
runIO (PrintLine msg x) = putStrLn msg *> return x
runIO (ReadLine k) = fmap k getLine

现在我们可以用 runIO 来填补 interpretTeletype 的其余部分。回想一下 teletypeF::TeletypeF (Teletype a)TeletypeF 仿函数的一层,它包含了 Free 计算的其余部分。我们将使用 runIO 来解释最外层(所以我们有 runIO teletypeF::IO (Teletype a))然后使用 IO monad 的 >>= 组合来解释返回的 Teletype a

interpretTeletype::Teletype a -> IO a
interpretTeletype (Pure x) = return x
interpretTeletype (Free teletypeF) = runIO teletypeF >>= interpretTeletype

foldFree 的定义只是 interpretTeletype 的定义,除了 runIO 函数已被考虑在内。因此,foldFree 独立于任何特定的基础仿函数和任何目标 monad 工作。

foldFree::Monad m => (forall x. f x -> m x) -> Free f a -> m a
foldFree eta (Pure x) = return x
foldFree eta (Free fa) = eta fa >>= foldFree eta

foldFree 的等级为 2:eta 是一种自然变形。我们本来可以给 foldFree 一种 Monad m => (f (Free f a) -> m (Free f a)) -> Free f a -> m a,但是这样可以让 eta 自由地检查 f 层内的 Free 计算。赋予 foldFree 这种更具限制性的类型可确保 eta 一次只能处理单个层。

iterM 确实赋予折叠功能检查子计算的能力。上一次迭代的(monadic)结果可用于 f 的参数中的下一个。iterM 类似于一个 paramorphism,foldFree 就像一个 catamorphism

iterM :: (Monad m, Functor f) => (f (m a) -> m a) -> Free f a -> m a
iterM phi (Pure x) = return x
iterM phi (Free fa) = phi (fmap (iterM phi) fa)