2013-03-06 25 views
10

Próbuję zrozumieć różnice między różnymi implementacjami koncepcji rur. Jedną z różnic między rurami i rur jest sposób, w jaki łączą one rury ze sobą. przewodów maJaka jest prawdziwa zaleta parametru typu upstream?

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

while rur mają

(>->) :: (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 

Jeśli rozumieć prawidłowo z rur gdy rurociąg z dwoma ogranicznikami, to jej efektem jest zwracany, a druga jest zatrzymany. W przypadku przewodu , jeśli lewa rura zakończy się, jej wynik zostanie przesłany w dół do prawej rury.

Zastanawiam się, jakie jest zalety podejścia kanał? Chciałbym zobaczyć przykład (najlepiej rzeczywisty), który jest łatwy do wdrożenia przy użyciu przewodów i >+>, ale trudny (e) do realizacji przy użyciu rur i >->.

Odpowiedz

5

Z mojego doświadczenia wynika, że ​​rzeczywiste korzyści z terminatorów upstream są bardzo wąskie, dlatego w tym momencie są one ukryte przed publicznym API. Myślę, że użyłem ich tylko w jednym kawałku kodu kiedykolwiek (wieloczęściowe przetwarzanie wai-extra).

W swojej najbardziej ogólnej formie rura pozwala na wygenerowanie zarówno strumienia wartości wyjściowych, jak i wyniku końcowego. Kiedy połączymy tę rurę z inną rurą dopływową, wówczas strumień wartości wyjściowych staje się strumieniem wejściowym strumienia, a końcowy wynik w górę strumienia staje się "końcowym terminatorem" w dalszej części strumienia. Z tej perspektywy posiadanie dowolnych terminatorów upstream pozwala na symetryczny interfejs API.

Jednak w praktyce bardzo rzadko korzysta się z takiej funkcjonalności, a ponieważ po prostu myli ona API, została ukryta w module .Internal z wersją 1.0. Jeden teoretyczny przypadek użycia może być następujący:

  • Masz źródło, które produkuje strumień bajtów.
  • Kanał, który zużywa strumień bajtów, oblicza wartość skrótu jako wynik końcowy i przekazuje wszystkie bajty w dół.
  • Zlew, który zużywa strumień bajtów, np. Do przechowywania ich w pliku.

Przy pomocy terminatorów upstream można połączyć te trzy elementy i uzyskać wynik z Conduit zwrócony jako ostateczny wynik potoku. Jednak w większości przypadków istnieje alternatywny, prostszy sposób osiągnięcia tych samych celów. W tym przypadku, można:

  1. Zastosowanie conduitFile przechowywać bajtów w pliku i obrócić przewód skrótu do mieszania zlewu i umieścić go w dół
  2. Zastosowanie zipSinks scalić zarówno umywalkę skrótu i ​​zapisywanie pliku zatopić się w jednym zlewie.
9

Klasycznym przykładem czegoś łatwiejszego do wdrożenia z conduit jest obecnie obsługa końca wejścia od źródła. Na przykład, jeśli chcesz spasować listę wartości i powiązać wynik w potoku, nie możesz tego zrobić w ramach pipes bez tworzenia dodatkowego protokołu pod numerem pipes. To jest właśnie to, co rozwiązuje biblioteka pipes-parse. To inżynier protokół Maybe na górze pipes, a następnie definiuje wygodne funkcje do wprowadzania danych wejściowych z góry, zgodnie z tym protokołem.

Na przykład, mają funkcję onlyK, który przyjmuje rurę i otacza wszystkie wyjścia w Just, a następnie kończy się Nothing:

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

Można również funkcję justK, który określa funktorem z rur że są Maybe -unaware do rur, które są Maybe -aware dla wstecznej kompatybilności

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 

A potem raz masz Producer, który przestrzega tego protokołu, możesz użyć wielu różnych analizatorów, które są abstrakcyjne dla ciebie. Najprostszym z nich jest draw:

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

Pobiera wartość typu a lub nie w transformatorze na ParseP proxy jeśli przed zabrakło wejścia. Możesz także wziąć wiele wartości naraz:

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 

... i kilka innych fajnych funkcji. Użytkownik nigdy nie musi w rzeczywistości bezpośrednio wchodzić w interakcję z końcem sygnału wejściowego.

Zwykle, gdy ludzie pytają o obsługę końcowych danych wejściowych, to, co naprawdę chcieli, było analizowanie, dlatego pipes-parse tworzy ramki z błędami końca wejścia jako podzestawu parsowania.

+0

Ciekawi mnie, jak ten protokół ma związek z kompozycją rur? Załóżmy, że mam potok 'readFileK', który odczytuje plik i wysyła' Nic' do sygnalizowania końca. Jeśli zrobię "(readFileK" file1 ">> readFileK" file2 ")> -> otherPipeK' to' otherPipeK' otrzyma 'Nothing' dwa razy? Z drugiej strony, jeśli mam plik 'readFileK'">> -> (pipe1K >> pipe2K) i dane wejściowe z pliku są wyczerpane podczas przetwarzania 'pipe1K', to' pipe2K' nigdy nie dowiaduje się, że dane wejściowe zostały już zapisane zubożony. –

+0

Dlatego 'onlyK' jest oddzielnym kombinatorem, a zachowanie' Nothing' nie jest wbudowane w źródła. W ten sposób możesz łączyć wiele źródeł w jeden, na przykład 'onlyK (readFileS" file "> => readSocketS socket)'. Twój drugi przykład nie powoduje żadnych problemów. Jeśli 'pipe1K' zabraknie danych wejściowych, to zawiedzie w' ParseP' i 'pipe2K' nigdy nie będzie działać. Żaden z parsujących prymitywów nie może przekroczyć końca znacznika wejściowego. –

Powiązane problemy