Si MonadPlus est la classe « générateur », alors quelle est la classe « consommateur » ?
-
26-12-2019 - |
Question
UN Pipe
peut être divisé en deux parties :le Générateur partie (yield
) et le consommateur partie (await
).
Si tu as un Pipe
qui n'utilise que la moitié de son générateur et ne renvoie que ()
(ou ne revient jamais), alors il peut être représenté comme un "ListT
bien fait".Il se trouve que MonadPlus
peut être utilisé pour représenter quelque chose comme ListT-done-right.
http://www.reddit.com/r/haskell/comments/2bpsh7/a_simple_monadic_stream_library/cj7sqtw?context=3
Voici donc ma question:Existe-t-il un dual vers ListT et MonadPlus pour la partie consommateur de Pipes ?
Exigences:
- Une pipe qui ne sert jamais
yield
, et ne renvoie que()
(ou ne revient jamais), mais utiliseawait
peut être représenté comme ceci "dual à ListT". - Le "dual de ListT" peut être généralisé au "dual de MonadPlus"
La solution
Je pense que la réponse n'est pas de dualiser la classe de types "de type générateur", mais plutôt de l'étendre avec un simple Category
instance équivalente à la await
/(>~)
catégorie de pipes
.
Malheureusement, il n'existe aucun moyen d'organiser les variables de type pour que cela satisfasse les trois classes de types (MonadPlus
, MonadTrans
, et Category
), je vais donc définir une nouvelle classe de type :
{-# 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
Les lois pour cette classe de types sont les lois des catégories :
await >~ f = f
f >~ await = f
(f >~ g) >~ h = f >~ (g >~ h)
Ensuite, vous pouvez implémenter les deux Consumer
sable Pipe
s une fois que vous avez cette classe de type supplémentaire :
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)
-}
Le plus difficile est de trouver comment procéder sans ajouter une nouvelle classe de type à base
.Je préférerais réutiliser l'original Category
tapez la classe si possible, ayant éventuellement await
et (>~)
soyez simplement des fonctions qui enveloppent votre type dans un nouveau type, utilisez le Category
par exemple, puis déballer-le, mais je travaille toujours sur les détails de la façon de procéder.
Modifier:J'ai trouvé la solution.Définissez simplement le nouveau type suivant :
{-# 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)
Alors n'importe quelle bibliothèque peut simplement implémenter un Category
exemple pour leur type enveloppé dans le Consumer
nouveau genre.
Vous obtiendrez alors une contrainte comme celle-ci à chaque fois que vous utiliserez await
ou (>~)
:
cat :: (MonadPlus (t a m), Category (Consumer t m)) => t a m a
cat = await `mplus` cat