如果MonadPlus是“生成器”类,那么“消费者”类是什么?

14

一个 Pipe 可以分为两个部分: 生成器(yield)和消费者(await)。

如果你有一个只使用其生成器部分并且只返回 ()(或永不返回)的 Pipe,那么它可以表示为“ListT做得好”。结果证明,MonadPlus 可以用来表示类似于正确执行 ListT 的任何东西。引用 Gabriel Gonzalez

Note that you can build any ListT (not just the one in pipes) with only a transformers dependency. For example, here is how you would implement a ListT analog of Pipes.Prelude.stdinLn:

-- 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

That will type check as any ListT out there and do the right thing for all of them.

我的问题是:在Pipes的消费者部分中,是否存在ListTMonadPlus的对偶?
要求:
  • 一个管道从不使用yield,只返回()(或永远不返回),但确实使用await,可以表示为"ListT的对偶"。
  • "ListT的对偶"可以推广到"MonadPlus的对偶"。

4
我认为这应该类似于FoldM,但我没有深入思考过。 - Michael Snoyman
2
也许是 SupplyT - bennofs
1个回答

10
我认为解决方案不是将“类似生成器的”类型类进行二元化,而是通过扩展一个简单的 `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)

当你拥有这个额外的类型类后,你就可以同时实现ConsumerPipe

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(>~)作为函数包装您的类型成一个新类型,使用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

这不完全是我要求的,但我想提一下 Category (Consumer t m) 的想法非常酷。 - Dan Burton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接