14

Aは、ジェネレーター部分 ( ) とコンシューマー部分 ( )Pipeの 2 つの部分に分けることができます。yieldawait

Pipeジェネレーターの半分のみを使用し、返すだけ(または決して返さない) がある場合()、それは「ListT正しく行われた」として表すことができます。ListT MonadPlus-done-right のようなものを表すために使用できることがわかりました。ガブリエル・ゴンザレスの引用

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

それはそこにあるものと同じようにチェックを入力ListTし、それらすべてに対して正しいことを行います。

だから私の質問はこれです:パイプのコンシューマー部分にデュアルがListTありますか?MonadPlus

要件:

  • yieldを使用せず、返すだけ()(または決して返さない) であるが、使用するパイプは、awaitこの「ListT のデュアル」として表すことができます。
  • 「ListT の双対」は「MonadPlus の双対」に一般化できます。
4

1 に答える 1

10

答えは、「ジェネレーターのような」型クラスを二重化することではなく、 の/カテゴリにCategory相当する単純なインスタンスで拡張することだと思います。await(>~)pipes

MonadPlus残念ながら、これを 3 つの型クラス ( 、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)

次に、この追加の型クラスがあれば、 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可能であれば元の型クラスを再利用したいと思います。おそらく、型を newtype でラップし、インスタンスを使用してからアンラップする関数を持っ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)

次に、どのライブラリでも、 newtypeCategoryでラップされた型のインスタンスを実装できます。Consumer

await次に、 orを使用するたびに、次のような制約が得られます(>~)

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