14

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

如果你有一个Pipe只使用它的生成器一半,并且只返回()(或从不返回),那么它可以表示为“ListT正确完成”。事实证明,它MonadPlus可以用来表示像 ListT-done-right 这样的任何东西。引用 Gabriel Gonzalez 的话

请注意,您可以ListT仅使用转换器依赖项构建任何(不仅仅是管道中的)。例如,以下是您将如何实现的ListT模拟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

这将像任何类型一样输入 checkListT并为所有这些做正确的事情。

ListT所以我的问题是这样的:管道的MonadPlus消费者部分是否有双重关系?

要求:

  • 一个从不使用yield,只返回()(或从不返回)但确实使用的管道await可以表示为这个“对 ListT”。
  • “dual to ListT”可以推广到“dual of MonadPlus”
4

1 回答 1

10

我认为答案不是二元化“类似生成器”的类型类,而是用一个简单的Category实例来扩展它,相当于.await(>~)pipes

不幸的是,没有办法安排类型变量来满足所有三个类型类(MonadPlusMonadTransCategory),所以我将定义一个新的类型类:

{-# 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)

然后,一旦你有了这个额外的类型类,你就可以同时实现Consumers 和s :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
{-
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实例,然后解包它,但我仍在研究如何做到这一点的细节.

编辑:我找到了解决方案。只需定义以下新类型:

{-# 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)

然后任何库都可以Category为其类型实现一个实例,该实例包含在新类型中Consumer

然后,您在任何时候使用awaitor时都会得到这样的约束(>~)

cat :: (MonadPlus (t a m), Category (Consumer t m)) => t a m a
cat = await `mplus` cat
于 2014-08-01T14:55:44.237 回答