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 usa await se puede representar como este "dual a ListT".
  • El "dual a ListT" se puede generalizar al "dual de MonadPlus"
¿Fue útil?

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. Consumerarena Pipes 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top