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

时间:2017-07-19 13:27:24

标签: haskell coroutine conduit haskell-pipes free-monad

Monad.Reader Issue 19中的“Coroutine Pipelines”文章中,作者定义了一个通用Coroutine类型:

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

我注意到此类型与FreeT包中的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中的核心数据类型为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中的核心数据类型为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
    

    我想可以编写基于Proxy的{​​{1}}或Pipe数据类型,所以我想知道为什么没有这样做?这是出于性能原因吗?

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

1 个答案:

答案 0 :(得分:4)

要回答您的第二个问题,让我们首先通过查看Free来简化问题。 Free f a允许您构造f形状的a 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))

然后斜视,我们可以看到我们真正需要做的就是“移除”f - 结构,就像我们之前“移除”m - 结构一样。所以,我们有Trampoline = Free Identity,因为Identity没有结构。反过来,这意味着这个蹦床是FreeT Identity Identity:一种具有微不足道形状的退化协程,无法使用效果来确定是否反弹或返回。这就是这个蹦床和蹦床monad变压器之间的区别:变压器允许弹跳与m - 动作交错。

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

直观地说,如果Free f是用于构造f形状的AST的monad,则FreeT m f是用于构造f的monad - 形状的AST与由提供的效果交错m。如果你稍微眯一下,这正是协同程序的正确性:一个完整​​的泛化,它对构造的AST的形状和用于构造它的效果类型进行参数化计算。