Если MonadPlus - это класс “generator”, то что такое класс “consumer”?
-
26-12-2019 - |
Вопрос
A Pipe
может быть разбит на две части:в генератор часть (yield
) и тот потребитель часть (await
).
Если у вас есть Pipe
это использует только половину своего генератора и возвращает только ()
(или никогда не возвращается), тогда это может быть представлено как "ListT
все сделано правильно".Получается , что MonadPlus
может использоваться для представления чего-либо вроде ListT-done-right.
http://www.reddit.com/r/haskell/comments/2bpsh7/a_simple_monadic_stream_library/cj7sqtw?context=3
Итак, мой вопрос заключается в следующем:Существует ли двойной доступ к ListT и к MonadPlus для потребительской части Pipes?
Требования:
- Труба, которая никогда не используется
yield
, и возвращает только()
(или никогда не возвращается), но используетawait
может быть представлен как этот "dual to ListT". - "Двойственный по отношению к ListT" может быть обобщен на "двойственный по отношению к MonadPlus".
Решение
Я думаю, что ответ заключается не в том, чтобы дуализировать класс типа "generator-like", а скорее расширить его с помощью простого Category
экземпляр, эквивалентный await
/(>~)
категория из pipes
.
К сожалению, нет способа упорядочить переменные типа так, чтобы это удовлетворяло всем трем классам типов (MonadPlus
, MonadTrans
, и Category
), поэтому я определю новый тип class:
{-# 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)
Тогда вы можете реализовать и то, и другое Consumer
ы и Pipe
s как только у вас появится этот дополнительный тип class:
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)
-}
Самое сложное - выяснить, как это сделать, не добавляя новый тип class в base
.Я бы предпочел повторно использовать оригинал Category
введите класс, если это возможно, возможно, имеющий await
и (>~)
просто будьте функциями, которые обертывают ваш тип в newtype, используйте 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)
Тогда любая библиотека может просто реализовать Category
экземпляр для их типа , завернутый в Consumer
новый тип.
Тогда вы получали бы подобное ограничение каждый раз, когда вы использовали await
или (>~)
:
cat :: (MonadPlus (t a m), Category (Consumer t m)) => t a m a
cat = await `mplus` cat