Se MonadPlus é o "gerador" de classe, então o que é o "consumidor" de classe?
-
26-12-2019 - |
Pergunta
Um Pipe
pode ser dividido em duas partes:o gerador de parte (yield
) e o consumidor parte (await
).
Se você tem um Pipe
que só usa o gerador de metade, e só retorna ()
(ou nunca retorna), ele pode ser representado como um "ListT
feito".Acontece que MonadPlus
pode ser usado para representar qualquer coisa como ListT-feito direito.
http://www.reddit.com/r/haskell/comments/2bpsh7/a_simple_monadic_stream_library/cj7sqtw?context=3
Então a minha pergunta é esta:Há uma dupla ListT e MonadPlus para o consumidor parte de Tubos?
Requisitos:
- Um tubo que nunca usa
yield
, e só retorna()
(ou nunca retorna), mas não usarawait
pode ser representado como esta "dupla ListT". - A "dupla ListT" pode ser generalizado para a "dupla de MonadPlus"
Solução
Eu acho que a resposta é não para dualize o "gerador-como" tipo de classe, mas, ao invés de estendê-lo com um simples Category
instância equivalente ao await
/(>~)
categoria pipes
.
Infelizmente, não há nenhuma maneira para organizar o tipo de variáveis para fazer esta satisfazer todas as três classes de tipo (MonadPlus
, MonadTrans
, e Category
), então vou definir um novo tipo de classe:
{-# 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
As leis para este tipo de classe é a categoria de leis:
await >~ f = f
f >~ await = f
(f >~ g) >~ h = f >~ (g >~ h)
Em seguida, você pode executar ambos Consumer
s e Pipe
s uma vez que você tenha este tipo de classe:
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)
-}
A parte difícil é descobrir como fazer isso sem a adição de um novo tipo de classe para base
.Eu prefiro reutilizar original Category
tipo de classe, se possível, possivelmente tendo await
e (>~)
ser apenas funções que envolva o seu tipo em um newtype, use o Category
instância e, em seguida, desembrulhe-lo, mas eu ainda estou trabalhando nos detalhes de como fazer isso.
Editar:Eu encontrei a solução.Apenas defina o seguinte 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)
Em seguida, a qualquer biblioteca pode simplesmente implementar um Category
exemplo para o seu tipo encapsulado em Consumer
newtype.
Em seguida, você poderia ficar uma restrição como este, a qualquer momento você usou await
ou (>~)
:
cat :: (MonadPlus (t a m), Category (Consumer t m)) => t a m a
cat = await `mplus` cat