Haskell的FreeT和Coroutine类型有什么关系

what is the relationship between Haskell's FreeT and Coroutine type

Monad.Reader Issue 19的"Coroutine Pipelines"篇文章中,作者定义了一个泛型Coroutine类型:

newtype Coroutine f m a = Coroutine
  { resume :: m (Either (f (Coroutine f m a)) a)
  }

我注意到这种类型与 FreeT type from the free 包非常相似:

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

newtype FreeT f m a = FreeT
  { runFreeT :: m (FreeF f a (FreeT f m a))
  }

看来FreeTCoroutine是同构的。以下是从一个映射到另一个的函数:

freeTToCoroutine
  :: forall f m a. (Functor f, Functor m) => FreeT f m a -> Coroutine f m a
freeTToCoroutine (FreeT action) = Coroutine $ fmap f action
  where
    f :: FreeF f a (FreeT f m a) -> Either (f (Coroutine f m a)) a
    f (Pure a) = Right a
    f (Free inner) = Left $ fmap freeTToCoroutine inner

coroutineToFreeT
  :: forall f m a. (Functor f, Functor m) => Coroutine f m a -> FreeT f m a
coroutineToFreeT (Coroutine action) = FreeT $ fmap f action
  where
    f :: Either (f (Coroutine f m a)) a -> FreeF f a (FreeT f m a)
    f (Right a) = Pure a
    f (Left inner) = Free $ fmap coroutineToFreeT inner

我有以下问题:

  1. FreeTCoroutine 类型之间有什么关系? "Coroutine Pipelines" 的作者为什么不使用 FreeT 类型而不是创建 Coroutine 类型?
  2. 自由 monad 和协程之间是否存在某种更深层次的关系?类型同构似乎并非巧合。
  3. 为什么 Haskell 中流行的流媒体库不是基于 FreeT

    pipes is Proxy中的核心数据类型:

    data Proxy a' a b' b m r
      = Request a' (a  -> Proxy a' a b' b m r )
      | Respond b  (b' -> Proxy a' a b' b m r )
      | M          (m    (Proxy a' a b' b m r))
      | Pure    r
    

    conduit is Pipe中的核心数据类型:

    data Pipe l i o u m r
      = HaveOutput (Pipe l i o u m r) (m ()) o
      | NeedInput (i -> Pipe l i o u m r) (u -> Pipe l i o u m r)
      | Done r
      | PipeM (m (Pipe l i o u m r))
      | Leftover (Pipe l i o u m r) l
    

    我想可以根据 FreeT 编写 ProxyPipe 数据类型,所以我想知道为什么没有完成?是性能原因吗?

    我在流行的流媒体库中看到的 FreeT 的唯一提示是 pipes-group,它使用 FreeT 对流中的项目进行分组。

为了回答您的第二个问题,让我们首先通过查看 Free 来简化问题。 Free f a 允许您构建 a 值的 f 形 AST,以便以后减少(也称为解释)。将文章中的 monad 转换器与未提升的自由构造进行比较时,我们可以简单地选择 Identity 代替 m,这是从它们的转换器构造基本 monad 的通常做法:Free f = FreeT Identity f.

Monad Reader 文章首先介绍了提升的 Trampoline monad 转换器,所以让我们从未提升的版本开始,Identity 被省略:

data Trampoline a = Return a | Bounce (Trampoline a)

如果我们将其与 Free

进行比较
data Free f r = Pure r | Free (f (Free f r))

稍微眯眼,我们可以看到我们真正需要做的就是 "remove" f 结构,就像我们之前 "removed" m -结构体。所以,我们有 Trampoline = Free Identity,同样是因为 Identity 没有结构。反过来,这意味着这个蹦床是一个FreeT Identity Identity:一种退化的协程,形状琐碎,无法使用效果来确定是弹跳还是return。这就是这个蹦床和蹦床单子变换器之间的区别:变换器允许弹跳与 m-actions.

交错

通过一些工作,我们还可以看到生成器和消费者是特定选择 f 的自由单子,分别为 ((,) a)((->) a)。他们的免费 monad 转换器版本同样允许他们交错 m-actions(例如,生成器可以在生成之前请求用户输入)。 Coroutine 概括了 f、AST 形状(对于 Trampoline 固定为 f ~ Identity)和可以交错的效果类型(固定为无效果,或 m ~ IdentityFree。这正是 FreeT m f.

直觉上,如果 Free f 是纯粹构造 f 形 AST 的单子,那么 FreeT m f 是构造 f 形 AST 与效果交错的单子由 m 提供。如果您稍微眯起眼睛,这正是协同程序的本质:一种完整的概括,它对构造的 AST 的形状和用于构造它的效果类型的具体计算进行参数化。