2013-03-06 15 views
10

Sto cercando di capire le differenze tra le diverse implementazioni del concetto di tubi. Una delle differenze tra conduit e pipe è il modo in cui si fondono insieme i tubi. condotto troviQual è il vero vantaggio del parametro di tipo upstream di conduit?

(>+>) :: Monad m 
     => Pipe l a b r0 m r1 -> Pipe Void b c r1 m r2 -> Pipe l a c r0 m r2 

mentre tubi hanno

(>->) :: (Monad m, Proxy p) 
     => (b' -> p a' a b' b m r) -> (c' -> p b' b c' c m r) -> c' -> p a' a c' c m r 

Se comprendo correttamente, con tubi, quando qualsiasi tubo dei due arresti, il risultato è restituito e l'altra è fermato Con il condotto , se il tubo sinistro è finito, il risultato viene inviato a valle nel tubo giusto.

Mi chiedo, qual è il vantaggio dell'approccio condotto? Mi piacerebbe vedere qualche esempio (preferibilmente nel mondo reale) che sia facile da implementare utilizzando conduit e >+>, ma difficile da implementare utilizzando pipe e >->.

risposta

5

In base alla mia esperienza, i vantaggi reali dei terminatori a monte sono molto ridotti, motivo per cui sono a questo punto nascosti all'API pubblica. Penso di averli usati solo in un pezzo di codice (l'analisi multiparte di wai-extra).

Nella sua forma più generale, un tubo consente di produrre sia un flusso di valori di output e un risultato finale. Quando si fondono quel tubo con un altro tubo downstream, quel flusso di valori di output diventa il flusso di input di downstream e il risultato finale di upstream diventa il "terminatore a monte" di downstream. Quindi, da quella prospettiva, disporre di terminatori a monte arbitrari consente un'API simmetrica.

Tuttavia, in pratica, è molto raro che tale funzionalità sia effettivamente utilizzata e, poiché confonde semplicemente l'API, è stata nascosta nel modulo .Internal con la versione 1.0. Un caso d'uso teorico potrebbe essere il seguente:

  • Si dispone di una sorgente che produce un flusso di byte.
  • Un conduit che consuma un flusso di byte, calcola un hash come risultato finale e trasmette tutti i byte a valle.
  • Un sink che consuma il flusso di byte, ad esempio per memorizzarli in un file.

Con i terminatori upstream, è possibile collegare questi tre e ottenere il risultato dal conduit restituito come risultato finale della pipeline. Tuttavia, nella maggior parte dei casi c'è un mezzo alternativo e più semplice per raggiungere gli stessi obiettivi. In questo caso, si potrebbe:

  1. Usa conduitFile per memorizzare i byte in un file e girare la Conduit hash in un hash Sink e metterlo a valle
  2. Usa zipSinks per unire sia un lavandino hash e una scrittura di file affondare in un unico lavandino.
9

Il classico esempio di qualcosa di più semplice da implementare con conduit attualmente sta gestendo la fine dell'input da upstream. Ad esempio, se si desidera piegare un elenco di valori e associare il risultato all'interno della pipeline, non è possibile farlo entro pipes senza aver progettato un protocollo aggiuntivo su pipes.

In realtà, questo è esattamente ciò che risolve la libreria pipes-parse imminente. Progetta un protocollo Maybe su pipes e quindi definisce le funzioni utili per il disegno di input da monte che rispettano tale protocollo.

Ad esempio, si ha la funzione onlyK, che prende un tubo e avvolge tutte le uscite di Just e poi si conclude con un Nothing:

onlyK :: (Monad m, Proxy p) => (q -> p a' a b' b m r) -> (q -> p a' a b' (Maybe b) m r) 

Hai anche la funzione justK, che definisce un funtore da tubi che sono Maybe -unaware ai tubi che sono Maybe -consapevoli per la compatibilità all'indietro

justK :: (Monad m, ListT p) => (q -> p x a x b m r) -> (q -> p x (Maybe a) x (Maybe b) m r) 

justK idT = idT 
justK (p1 >-> p2) = justK p1 >-> justK p2 

E poi una volta che hai un Producer che rispetta tale protocollo è possibile utilizzare una grande varietà di parser che si astraggono per il controllo Nothing. Il più semplice è draw:

draw :: (Monad m, Proxy p) => Consumer (ParseP a p) (Maybe a) m a 

Si recupera un valore di tipo a o fallisce nel trasformatore ParseP proxy se a monte corto di ingresso. Puoi anche prendere più valori contemporaneamente:

drawN :: (Monad m, Proxy p) => Int -> Consumer (ParseP a p) (Maybe a) m [a] 

drawN n = replicateM n draw -- except the actual implementation is faster 

... e molte altre funzioni. L'utente non deve mai interagire direttamente con la fine del segnale di input.

Di solito quando le persone chiedono la gestione di fine input, ciò che volevano veramente era l'analisi, ed è per questo che i problemi di end-of-input dei frame pipes-parse sono un sottoinsieme di analisi.

+0

Sono curioso, come va questo protocollo insieme alla componibilità dei tubi? Supponiamo che io abbia una pipe 'readFileK' che legge un file e invia' Nothing' per segnalare la fine. Se faccio '(readFileK" file1 ">> readFileK" file2 ")> -> otherPipeK' quindi' otherPipeK' riceve 'Nothing' due volte? E d'altra parte, se ho 'readFileK" file' "> -> (pipe1K >> pipe2K) e l'input dal file è esaurito mentre' pipe1K' sta elaborando allora 'pipe2K' non apprende mai che l'input è già stato impoverito. –

+0

Ecco perché 'onlyK' è un combinatore separato e il comportamento' Nothing' non è integrato nelle origini. In questo modo puoi combinare più fonti in una sola, ad esempio 'onlyK (readFileS" file "> => socket readSocketS)'. Il tuo secondo esempio non causa alcun problema. Se 'pipe1K' finisce l'input fallirà in' ParseP' e 'pipe2K' non funzionerà mai. Nessuno dei primitivi di analisi è in grado di superare la fine del marker di input. –

Problemi correlati