Si MonadPlus es la clase "generadora", ¿cuál es entonces la clase "consumidora"?
-
26-12-2019 - |
Pregunta
A Pipe
se puede dividir en dos partes:el generador parte (yield
) y el consumidor parte (await
).
Si tienes un Pipe
que solo usa la mitad de su generador y solo regresa ()
(o nunca regresa), entonces se puede representar como "ListT
bien hecho".Resulta que MonadPlus
se puede utilizar para representar cualquier cosa como ListT-done-right.
http://www.reddit.com/r/haskell/comments/2bpsh7/a_simple_monadic_stream_library/cj7sqtw?context=3
Así que mi pregunta es esta:¿Existe una dualidad entre ListT y MonadPlus para la parte de consumo de Pipes?
Requisitos:
- Una pipa que nunca usa
yield
, y solo regresa()
(o nunca regresa), pero usaawait
se puede representar como este "dual a ListT". - El "dual a ListT" se puede generalizar al "dual de MonadPlus"
Solución
Creo que la respuesta no es dualizar la clase de tipo "tipo generador", sino ampliarla con una simple Category
instancia equivalente a la await
/(>~)
categoría de pipes
.
Desafortunadamente, no hay manera de organizar las variables de tipo para que esto satisfaga las tres clases de tipo (MonadPlus
, MonadTrans
, y Category
), así que definiré una nueva clase de tipo:
{-# 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
Las leyes para esta clase de tipo son las leyes de categoría:
await >~ f = f
f >~ await = f
(f >~ g) >~ h = f >~ (g >~ h)
Entonces puedes implementar ambos. Consumer
arena Pipe
s una vez que tenga esta clase de tipo adicional:
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)
-}
La parte difícil es descubrir cómo hacer esto sin agregar una nueva clase de tipo a base
.Preferiría reutilizar el original. Category
clase de tipo si es posible, posiblemente teniendo await
y (>~)
solo sean funciones que envuelvan su tipo en un nuevo tipo, use el Category
instancia, y luego desenvolverlo, pero todavía estoy resolviendo los detalles de cómo hacerlo.
Editar:Encontré la solución.Simplemente defina el siguiente nuevo tipo:
{-# 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)
Entonces cualquier biblioteca puede simplemente implementar un Category
instancia para su tipo envuelto en el Consumer
nuevo tipo.
Entonces obtendrías una restricción como esta cada vez que usaras await
o (>~)
:
cat :: (MonadPlus (t a m), Category (Consumer t m)) => t a m a
cat = await `mplus` cat