2013-03-06 7 views
10

Я пытаюсь понять различия между различными реализациями концепции труб. Одно из отличий между трубопроводами и - это то, как они соединяют трубы вместе. Conduit имеетКакова реальная польза от параметра типа восходящего потока канала?

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

в то время как трубы имеют

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

Если я правильно понимаю, с труб, когда любая труба из двух упоров, его результат возвращается, а другой останавливается. При кабелепровод, если левая труба закончена, ее результат направляется вниз по потоку к правой трубе.

Интересно, какая польза от кабелепровод? Я хотел бы увидеть некоторый пример (желательно в реальном мире), который легко реализовать, используя кабелепровод и >+>, но жесткий (эр) для реализации с использованием труб и >->.

ответ

5

По моему опыту, преимущества терминалов верхнего уровня в реальном времени очень тонкие, поэтому на данный момент они скрыты от публичного API. Я думаю, что я использовал их только в одном фрагменте кода (многопартийный анализ wai-extra).

В своем наиболее общем виде труба позволяет производить как поток выходных значений, так и конечный результат. Когда вы соединяете эту трубку с другой нисходящей трубкой, тогда поток выходных значений становится входным потоком нижестоящего потока, а конечный результат восходящего потока становится «вверх по течению». Поэтому с этой точки зрения наличие произвольных терминаторов верхнего уровня допускает симметричный API.

Однако на практике очень редко используется такая функциональность, и поскольку она просто путает API, она была скрыта в модуле .Internal с выпуском 1.0. Один теоретический вариант использования может быть следующим:

  • У вас есть источник, который создает поток байтов.
  • A Conduit, который потребляет поток байтов, вычисляет хеш в качестве конечного результата и передает все байты ниже по потоку.
  • Раковина, которая потребляет поток байтов, например, для хранения их в файле.

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

  1. Использования conduitFile для хранения байт в файл и превратить хэш Conduit в хэш Слив и поместите его вниз по течению
  2. Использование zipSinks сливаться как хэш-мойки и запись файлов погрузиться в одну раковину.
9

Классический пример чего-то более легкого в использовании с conduit в настоящее время обрабатывает конец ввода от восходящего потока. Например, если вы хотите сбросить список значений и связать результат в конвейере, вы не сможете сделать это в пределах pipes без разработки дополнительного протокола поверх pipes.

Фактически, это именно то, что решает новая библиотека pipes-parse. Он разрабатывает протокол Maybe поверх pipes, а затем определяет удобные функции для ввода входных данных с восходящего потока, которые уважают этот протокол.

Например, имеет функцию onlyK, которая принимает трубку и оборачивает все выходы в Just, а затем заканчивает с Nothing:

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

Вы также иметь функцию justK, которая определяет функтор из труб которые являются Maybe -unaware к трубам, которые Maybe -aware для обратной совместимости

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 

И затем, как только у вас есть Producer, который уважает этот протокол, вы можете использовать большое разнообразие парсеров, которые аннотация над Nothing проверит вас. Простым является draw:

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

Он извлекает значение типа a или не в ParseP прокси трансформатора, если вверх по течению выбежала ввода. Вы также можете взять сразу несколько значений:

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 

... и несколько других приятных функций. Пользователь никогда не должен напрямую взаимодействовать с концом входного сигнала.

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

+0

Мне любопытно, как этот протокол идет вместе с композицией труб? Предположим, что у меня есть труба 'readFileK', которая читает файл и посылает' Nothing', чтобы сигнализировать о завершении. Если я делаю '(readFileK" file1 ">> readFileK" file2 ")> -> otherPipeK', то' otherPipeK' получает 'Nothing' дважды? С другой стороны, если у меня есть файл readFileK''> -> (pipe1K >> pipe2K), и вход из файла исчерпан, а 'pipe1K' обрабатывается, тогда' pipe2K' никогда не узнает, что вход уже истощены. –

+0

Вот почему 'onlyK' - это отдельный комбинатор, а поведение' Nothing' не встроено в источники. Таким образом вы можете объединить несколько источников в один, например 'onlyK (readFileS" file "> => readSocketS socket). Второй пример не вызывает никаких проблем. Если 'pipe1K' заканчивается, он не будет работать в' ParseP', а 'pipe2K' никогда не будет запущен. Ни один из примитивов синтаксического анализа не может пройти мимо конца входного маркера. –