Какая польза от остатков трубопровода?

Я пытаюсь понять разницу между каналом и трубами. В отличие от каналов, кондуит имеет понятие остатков. Чем полезны остатки? Я хотел бы увидеть несколько примеров, когда остатки необходимы.

А поскольку в трубах нет концепции остатков, можно ли добиться с ними аналогичного поведения?


person Petr    schedule 06.03.2013    source источник


Ответы (2)


Замечание Габриэля о том, что остатки всегда являются частью синтаксического анализа, интересно. Я не уверен, что соглашусь, но это может зависеть от определения синтаксического анализа.

Существует большая категория вариантов использования, которые требуют остатков. Синтаксический анализ, безусловно, один: каждый раз, когда синтаксический анализ требует какого-либо просмотра вперед, вам понадобятся остатки. Одним из примеров этого является функция getIndented пакета markdown, которая изолирует все последующие строки с определенным уровнем отступа, оставляя остальные строки для обработки позже.

Но гораздо более приземленный набор примеров живет в самом канале. Каждый раз, когда вы имеете дело с упакованными данными (такими как ByteString или Text), вам нужно будет прочитать фрагмент, как-то проанализировать его, использовать оставшееся, чтобы убрать лишнее, а затем сделать что-то с исходным содержимым. Возможно, самым простым примером этого является dropWhile.

На самом деле, я считаю, что остатки — это такая основная, базовая функция потоковой библиотеки, что новый интерфейс 1.0 для канала даже не предоставляет пользователям возможность отключить остатки. Я знаю очень мало реальных случаев использования, которые так или иначе не нужны.

person Michael Snoyman    schedule 07.03.2013
comment
Спасибо за объяснение, теперь я вполне уверен. Тем временем я думал о том, как реализовать остатки поверх библиотеки, похожей на конвейер, в которой их изначально нет. Идея состоит в том, что канал с остатками можно представить как канал, возвращающий (Maybe i, r). Моя попытка (для канала) находится здесь. - person Petr; 07.03.2013
comment
Я думаю, у вас правильная интуиция, ваша реализация очень похожа на то, как все работает внутри. Я думаю, вы обнаружили проблему двойного остатка, поэтому остатки могут быть сложены в виде нескольких конструкторов Leftover в канале. - person Michael Snoyman; 08.03.2013
comment
Тем не менее еще одна попытка (которую я, скорее всего, буду использовать в своем Scala library) для остатков состоит в том, чтобы рассматривать остатки как своего рода обратную связь: Для Pipe Void i (Either i o) u m r мы отправляем любой Left i обратно на его вход с помощью внутреннего метода, который преобразует такой канал в стандартный. - person Petr; 13.03.2013
comment
Не могли бы вы указать на простой пример остатков? Я посмотрел на вашу ссылку dropWhile, но она 404s. Что осталось в случае dropWhile? Если возможно, не могли бы вы посмотреть stackoverflow.com/q/44402598/409976 ? Спасибо! - person Kevin Meredith; 09.06.2017
comment
Нажмите пробел, чтобы просмотреть слайды отсюда: snoyman.com/reveal/conduit -йесод#/13/1. Это также описано в руководстве по Conduit, которое я рекомендую прочитать: haskell-lang.org/library/conduit. . - person Michael Snoyman; 09.06.2017

Я отвечу за pipes. Короткий ответ на ваш вопрос заключается в том, что будущая библиотека pipes-parse будет поддерживать остатки как часть более общей структуры синтаксического анализа. Я обнаружил, что почти в каждом случае, когда людям нужны остатки, им действительно нужен синтаксический анализатор, поэтому я формулирую проблему остатков как подмножество синтаксического анализа. Вы можете найти текущий проект библиотеки здесь.

Однако, если вы хотите понять, как pipes-parse заставляет его работать, самый простой способ реализовать остатки — просто использовать StateP для хранения буфера обратной передачи. Для этого требуется только определить следующие две функции:

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 сначала обращается к буферу обратной передачи, чтобы увидеть, есть ли какие-либо сохраненные элементы, извлекая один элемент из стека, если он доступен. Если буфер пуст, он вместо этого запрашивает новый элемент из восходящего потока. Конечно, нет смысла иметь буфер, если мы не можем ничего поместить обратно, поэтому мы также определяем unDraw для помещения элемента в стек для сохранения на потом.

Редактировать: Ой, я забыл включить полезный пример того, когда остатки полезны. Как говорит Майкл, takeWhile и dropWhile — полезные случаи остатков. Вот функция drawWhile (аналогично тому, что Майкл называет 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 []

Теперь представьте, что вашим продюсером был:

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

... и вы подключили это к потребителю, который использовал:

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

Если бы первый drawWhile odd не отодвинул последний элемент, который он нарисовал, вы бы отбросили 4, который не был бы правильно передан второму оператору drawWhile even.

person Gabriel Gonzalez    schedule 06.03.2013