Wenn MonadPlus die „Generator“-Klasse ist, was ist dann die „Verbraucher“-Klasse?
-
26-12-2019 - |
Frage
A Pipe
kann in zwei Teile zerlegt werden:Die Generator Teil (yield
) und das Verbraucher Teil (await
).
Wenn Sie eine haben Pipe
das nutzt nur die Generatorhälfte und gibt nur zurück ()
(oder nie zurückkehrt), dann kann es dargestellt werden als „ListT
richtig gemacht".Es stellt sich heraus, dass MonadPlus
kann verwendet werden, um etwas wie ListT-done-right darzustellen.
http://www.reddit.com/r/haskell/comments/2bpsh7/a_simple_monadic_stream_library/cj7sqtw?context=3
Meine Frage lautet also:Gibt es ein Dual zu ListT und zu MonadPlus für den Verbraucherteil von Pipes?
Anforderungen:
- Eine Pfeife, die nie benutzt wird
yield
, und kehrt nur zurück()
(oder kehrt nie zurück), wird aber verwendetawait
kann als „Dual zu ListT“ dargestellt werden. - Das „Dual zu ListT“ kann auf das „Dual von MonadPlus“ verallgemeinert werden.
Lösung
Ich denke, die Antwort besteht nicht darin, die „generatorartige“ Typklasse zu dualisieren, sondern sie um eine einfache zu erweitern Category
Instanz äquivalent zu await
/(>~)
Kategorie von pipes
.
Leider gibt es keine Möglichkeit, die Typvariablen so anzuordnen, dass alle drei Typklassen erfüllt werden (MonadPlus
, MonadTrans
, Und Category
), also werde ich eine neue Typklasse definieren:
{-# 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
Die Gesetze für diese Typklasse sind die Kategoriegesetze:
await >~ f = f
f >~ await = f
(f >~ g) >~ h = f >~ (g >~ h)
Dann können Sie beides umsetzen Consumer
s und Pipe
s, sobald Sie diese zusätzliche Typklasse haben:
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)
-}
Der schwierige Teil besteht darin, herauszufinden, wie man das macht, ohne eine neue Typklasse hinzuzufügen base
.Ich würde lieber das Original wiederverwenden Category
Typklasse wenn möglich, möglicherweise mit await
Und (>~)
Seien Sie einfach Funktionen, die Ihren Typ in einen neuen Typ einschließen. Verwenden Sie die Category
Beispiel, und packen Sie es dann aus, aber ich arbeite immer noch an den Einzelheiten, wie das geht.
Bearbeiten:Ich habe die Lösung gefunden.Definieren Sie einfach den folgenden neuen Typ:
{-# 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)
Dann kann jede Bibliothek einfach a implementieren Category
Beispiel für ihren Typ, der in die eingebettet ist Consumer
neuer Typ.
Dann würden Sie bei jeder Verwendung eine solche Einschränkung erhalten await
oder (>~)
:
cat :: (MonadPlus (t a m), Category (Consumer t m)) => t a m a
cat = await `mplus` cat