Pipe
可以分为两部分:生成器部分(yield
(和消费者部分(await
(。
如果你有一个只使用生成器一半的Pipe
,并且只返回()
(或从不返回(,那么它可以表示为"ListT
做对了"。事实证明,MonadPlus
可以用来表示像ListT-done-right这样的任何东西。引用加布里埃尔·冈萨雷斯的话:
请注意,您可以仅使用转换器依赖项构建任何
ListT
(不仅仅是管道中的(。例如,下面介绍了如何实现Pipes.Prelude.stdinLn
的ListT
模拟:-- stdinLn :: ListT IO String stdinLn :: (MonadTrans t, MonadPlus (t IO)) => t IO String stdinLn = do eof <- lift isEOF if eof then mzero else do str <- lift getLine return str `mplus` stdinLn
这将像任何
ListT
一样键入检查,并为所有人做正确的事情。
所以我的问题是:对于Pipes的消费者部分,是否有双重ListT
和MonadPlus
?
要求:
- 从不使用
yield
,只返回()
(或从不返回(但确实使用await
的管道可以表示为"对偶到ListT"。
"双重到 - ListT"可以推广到"MonadPlus的双重">
我认为答案不是将"类似生成器"的类型类二元化,而是用一个简单的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
s 和 Pipe
s:
printer :: (Show a, Monad (t a IO), MonadTrans (t a), Consumer t) => t a IO r
printer = do
a <- await
lift (print a)
printer
{-
printer :: Show a => Consumer 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
{-
cat :: Monad m => Pipe a a m r
cat = do
a <- await
yield a
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
{-
debug :: Show a => Pipe a a IO r
debug = do
a <- await
lift (print a)
yield a
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)
{-
taker :: Monad m => Int -> Pipe a a m ()
taker 0 = return ()
taker n = do
a <- await
yield a
taker (n - 1)
-}
困难的部分是弄清楚如何在不向base
添加新类型类的情况下执行此操作。 如果可能的话,我更愿意重用原始的 Category
类型类,可能await
和(>~)
只是将您的类型包装在 newtype 中的函数,使用 Category
实例,然后解包它,但我仍在研究如何做到这一点的细节。
编辑:我找到了解决方案。 只需定义以下新类型:
{-# 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)
然后,任何库都可以为包装在 newtype 中的类型实现一个Category
实例Consumer
。
您使用await
或(>~)
时,您都会得到这样的约束:
cat :: (MonadPlus (t a m), Category (Consumer t m)) => t a m a
cat = await `mplus` cat