您要查找的术语是
自由单子变换器。学习这些内容的最佳方式是阅读
The Monad Reader第19期中的“协程管道”文章。Mario Blazevic非常清晰地描述了这种类型的工作原理,但他将其称为“Coroutine”类型。
我在
transformers-free
软件包中编写了他的类型,然后它被合并到
free
软件包中,这是它的新官方主页。
您的
Callback
类型是同构的:
type Callback a = forall r . FreeT ((->) a) IO r
为了理解自由单子变换器,您需要首先
理解自由单子,它们只是抽象语法树。您给自由单子一个定义语法树中单个步骤的函子,然后它从该函子创建一个
Monad
,该
Monad
基本上是这些类型步骤的列表。因此,如果您有:
Free ((->) a) r
这是一个语法树,接受零个或多个 a
作为输入,然后返回一个值 r
。
然而,通常我们希望嵌入效果或使语法树的下一步取决于某些效果。为此,我们只需将自由单子提升为自由单子变换器,它在语法树步骤之间交错基本单子。在您的 Callback
类型的情况下,您正在在每个输入步骤之间交替使用 IO
,因此您的基本单子是 IO
:
FreeT ((->) a) IO r
自由单子的好处在于它们自动成为任何函子的单子,因此我们可以利用这一点来使用
do
符号来组装我们的语法树。例如,我可以定义一个
await
命令,以将输入绑定在单子中。
import Control.Monad.Trans.Free
await :: (Monad m) => FreeT ((->) a) m a
await = liftF id
现在我有一个用于编写回调函数的 DSL:
Callback
。
import Control.Monad
import Control.Monad.Trans.Free
printer :: (Show a) => FreeT ((->) a) IO r
printer = forever $ do
a <- await
lift $ print a
请注意,我从未必须定义必要的
Monad
实例。对于任何函子
f
,
FreeT f
和
Free f
都自动成为
Monad
,在这种情况下,
((->) a)
是我们的函子,因此它会自动执行正确的操作。这就是范畴论的魔力!
此外,我们从未必须定义
MonadTrans
实例才能使用
lift
。
FreeT f
自动成为一个单子变换器,只要给定任何函子
f
,所以它也为我们处理了这个问题。
我们的打印机是一个合适的
Callback
,所以我们可以通过分解自由单子变换器来馈送值。
feed :: [a] -> FreeT ((->) a) IO r -> IO ()
feed as callback = do
x <- runFreeT callback
case x of
Pure _ -> return ()
Free k -> case as of
[] -> return ()
b:bs -> feed bs (k b)
实际的打印发生在我们绑定 "runFreeT回调" 时,它给了我们语法树中的下一步,然后我们将其提供给列表的下一个元素。
让我们试试:
>>> feed [1..5] printer
1
2
3
4
5
然而,你甚至不需要自己编写所有这些内容。正如Petr所指出的,我的pipes
库为您抽象了这种常见的流模式。你的回调函数只需是:
forall r . Consumer a IO r
使用 pipes
定义 printer
的方式是:
printer = forever $ do
a <- await
lift $ print a
...我们可以像这样向其提供值列表:
>>> runEffect $ each [1..5] >-> printer
1
2
3
4
5
我设计了
pipes
以涵盖各种流抽象,使您始终可以使用
do
符号来构建每个流组件。此外,
pipes
还提供了许多优雅的解决方案,例如状态和错误处理,以及双向信息流。因此,如果您将
Callback
抽象公式化为
pipes
,您可以免费使用大量有用的机制。
如果您想了解更多关于
pipes
的内容,我建议您
阅读本教程。
Free
--Free f a = Either a (f (Either a (f (Either a (f ...)
-- 和Cofree
--Cofree f a = (a, f (a, f (a, f ...)
,只不过用(->)
代替了和/积。所以T f a = a -> f (a -> f (a -> f ...
,这使它是逆变的,与其他两个不同。 - shachaf(->)
不是可交换的,您还可以获得newtype Bar f a = Bar { unBar :: f (Bar f a) -> a }
,当f
是Contravariant
时,它是一个Functor
,而不是相反。) - shachaf