2013-03-06 15 views
13

Próbuję zrozumieć różnice między rurami i rur. W przeciwieństwie do rur, kanał ma koncepcję resztek. Do czego przydatne są resztki? Chciałbym zobaczyć kilka przykładów, w których resztki są niezbędne.Jaka jest korzyść z pozostałości po kanałach?

A ponieważ rur nie mają pojęcia resztki, czy jest jakiś sposób, aby osiągnąć podobny problem z nimi?

Odpowiedz

17

Punktem zainteresowania Gabriela jest to, że resztki są zawsze częścią analizowania. Nie jestem pewien, czy się zgodzę, ale to może zależeć od definicji parsowania.

Istnieje duża kategoria przypadków użycia, które wymagają resztek. Parsowanie to z pewnością jeden: za każdym razem, gdy analiza wymaga jakiegoś wcześniejszego przeglądania, będziesz potrzebować resztek. Jednym z przykładów jest funkcja getIndented pakietu pakowania, która izoluje wszystkie nadchodzące linie z pewnym poziomem wcięcia, pozostawiając resztę linii do przetworzenia później.

Ale dużo bardziej przyziemny zestaw przykładów żyje w samym kanale. Za każdym razem, gdy masz do czynienia z zapakowanymi danymi (takimi jak ByteString lub Text), musisz przeczytać fragment, przeanalizować go w jakiś sposób, użyć resztek, aby odeprzeć dodatkowy, a następnie zrobić coś z oryginalną treścią.Być może najprostszym tego przykładem jest dropWhile.

W rzeczywistości, uważam pozostały za tak podstawową, podstawową cechę biblioteki strumieniowej, że nowy interfejs 1.0 dla kanału nie wystawia nawet opcji użytkownikom na wyłączanie resztek. Znam bardzo niewiele przypadków użycia w realnym świecie, które nie potrzebują tego w taki czy inny sposób.

+0

Dzięki za wyjaśnienie, jestem już całkiem przekonany. Tymczasem zastanawiałem się, jak zaimplementować pozostałości po bibliotece podobnej do przewodnika, która nie ma ich natywnie. Chodzi o to, że przewód z resztkami może być reprezentowany jako kanał zwracający '(Maybe i, r)'. Moją próbą (dla przewodnika) jest [tutaj] (https://gist.github.com/ppetr/5110909). –

+0

Myślę, że masz odpowiednią intuicję, twoja implementacja jest bardzo podobna do tego, jak działa wewnętrznie. Wydaje mi się, że odkryłeś kwestię podwójnego pozostawienia, dlatego resztki mogą być układane jako wiele pozostawionych konstruktorów w przewodzie. –

+0

Jeszcze [kolejna próba] (https://gist.github.com/ppetr/5110909#file-feedback-hs) (której najprawdopodobniej użyję w mojej bibliotece Scala) dla resztek to zobaczenie resztek jako pewnego rodzaju feedback: Dla 'Pipe Void i (Either io) umr' wysyłamy dowolne' Left i' z powrotem do jego wejścia za pomocą wewnętrznej metody, która przekształca taką rurę w standardową. –

15

Odpowiem za pipes. Krótka odpowiedź na twoje pytanie jest taka, że ​​nadchodząca biblioteka pipes-parse będzie obsługiwać resztki w ramach bardziej ogólnego schematu analizowania. Uważam, że prawie każdy przypadek, w którym ludzie chcą resztek, które faktycznie chcą parsera, dlatego ustawiam problem z resztkami jako podzbiór parsowania. Możesz znaleźć aktualny projekt biblioteki here.

Jednakże, jeśli chcesz zrozumieć, w jaki sposób pipes-parse uruchamia to, najprostszym sposobem na wdrożenie resztek jest użycie StateP do przechowywania bufora wstecznego. To wymaga zdefiniowania tylko dwie następujące funkcje:

import Control.Proxy 
import Control.Proxy.Trans.State 

draw :: (Monad m, Proxy p) => StateP [a] p() a b' b m a 
draw = do 
    s <- get 
    case s of 
     [] -> request() 
     a:as -> do 
      put as 
      return a 

unDraw :: (Monad m, Proxy p) => a -> StateP [a] p() a b' b m() 
unDraw a = do 
    as <- get 
    put (a:as) 

draw najpierw konsultuje bufor Pushback, aby zobaczyć, czy są jakieś przechowywane elementy, popping jeden element ze stosu jeśli są dostępne. Jeśli bufor jest pusty, zamiast tego żąda nowego elementu od źródła. Oczywiście, nie ma sensu posiadanie bufora, jeśli nie możemy niczego odepchnąć, więc definiujemy również unDraw, aby wypchnąć element na stos, aby zapisać go na później.

Edytuj: Ups, zapomniałem podać użyteczny przykład, kiedy resztki są przydatne. Tak jak Michael mówi, takeWhile i dropWhile są przydatnymi przypadkami resztek. Oto funkcja drawWhile (analogicznie do tego, co Michael nazywa takeWhile):

drawWhile :: (Monad m, Proxy p) => (a -> Bool) -> StateP [a] p() a b' b m [a] 
drawWhile pred = go 
    where 
    go = do 
     a <- draw 
     if pred a 
     then do 
      as <- go 
      return (a:as) 
     else do 
      unDraw a 
      return [] 

Teraz wyobraź sobie, że producent był:

producer() = do 
    respond 1 
    respond 3 
    respond 4 
    respond 6 

... i że związał się z konsumentem, które kiedyś:

consumer() = do 
    evens <- drawWhile odd 
    odds <- drawWhile even 

Jeśli pierwszy nie odepchnie ostatniego elementu, który narysował, to zrzucisz 4, który nie będzie poprawnie przekazane do drugiego oświadczenia drawWhile even ".

Powiązane problemy