我认为解决方案不是将“类似生成器的”类型类进行二元化,而是通过扩展一个简单的 `Category` 实例,相当于 `pipes` 的 `await`/`(>~)` 类别。
不幸的是,没有办法安排类型变量使其同时满足三个类型类(`MonadPlus`、`MonadTrans` 和 `Category`),因此我将定义一个新的类型类。
{-# LANGUAGE KindSignatures #-}
import Control.Monad
import Control.Monad.Trans.Class
class Consumer (t :: * -> (* -> *) -> * -> *) where
await :: t a m a
(>~) :: t a m b -> t b m c -> t a m c
这个类型类的规律是分类规律:
await >~ f = f
f >~ await = f
(f >~ g) >~ h = f >~ (g >~ h)
当你拥有这个额外的类型类后,你就可以同时实现Consumer
和Pipe
:
printer :: (Show a, Monad (t a IO), MonadTrans (t a), Consumer t) => t a IO r
printer = do
a <- await
lift (print a)
printer
cat :: (MonadPlus (t a m), Consumer t) => t a m a
cat = await `mplus` cat
debug :: (Show a, MonadPlus (t a IO), MonadTrans (t a), Consumer t) => t a IO a
debug = do
a <- await
lift (print a)
return a `mplus` debug
taker :: (Consumer t, MonadPlus (t a m)) => Int -> t a m a
taker 0 = mzero
taker n = do
a <- await
return a `mplus` taker (n - 1)
艰难的部分在于如何在不向
base
添加新类型类的情况下完成此操作。如果可能的话,我更愿意重用原始的
Category
类型类,可能只需要将
await
和
(>~)
作为函数包装您的类型成一个新类型,使用
Category
实例,然后取消包装它,但我仍在研究如何具体实现这一点。
编辑:我找到了解决方案。只需定义以下newtype即可:
{-# LANGUAGE KindSignatures, FlexibleContexts #-}
import Control.Category
import Prelude hiding ((.), id)
newtype Consumer t m a b = Consumer { unConsumer :: t a m b }
await :: Category (Consumer t m) => t a m a
await = unConsumer id
(>~) :: Category (Consumer t m) => t a m b -> t b m c -> t a m c
f >~ g = unConsumer (Consumer f >>> Consumer g)
然后任何库都可以为其类型包装在Consumer
新类型中实现一个Category
实例。
这样,每当您使用await
或(>~)
时,您都会得到像这样的约束:
cat :: (MonadPlus (t a m), Category (Consumer t m)) => t a m a
cat = await `mplus` cat
FoldM
,但我没有深入思考过。 - Michael Snoyman