Какова реальная выгода от параметра типа восходящего канала?
Я пытаюсь понять различия между различными реализациями концепции труб. Одно из различий между трубопроводом и трубами заключается в том, как они соединяют трубы вместе. Трубопровод имеет
(>+>) :: 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
Если я правильно понимаю, с трубами, когда любая труба из двух останавливается, возвращается ее результат, а другая останавливается. В случае с трубопроводом, если левая труба закончена, ее результат направляется вниз по течению к правой трубе.
Интересно, в чем выгода подхода кабелепровода? Я хотел бы увидеть пример (желательно реальный), который легко реализовать, используя >+>
, но трудно (э) реализовать с помощью труб и >->
,
2 ответа
По моему опыту, реальные преимущества восходящих терминаторов очень невелики, поэтому на данный момент они скрыты от общедоступного API. Я думаю, что я использовал их только в одном куске кода (многоэлементный разбор wai-extra).
В наиболее общем виде труба позволяет создавать как поток выходных значений, так и конечный результат. Когда вы соединяете этот канал с другим нижестоящим каналом, этот поток выходных значений становится входным потоком нисходящего потока, а конечный результат восходящего потока становится "терминатором восходящего потока". Таким образом, с этой точки зрения, наличие произвольных терминаторов восходящего потока допускает симметричный API.
Однако на практике очень редко такие функциональные возможности используются, и, поскольку они просто путают API, они были скрыты в модуле.Internal с выпуском 1.0. Один теоретический вариант использования может быть следующим:
- У вас есть источник, который производит поток байтов.
- Канал, который потребляет поток байтов, вычисляет хэш как конечный результат и передает все байты в нисходящем направлении.
- Sink, который потребляет поток байтов, например, чтобы сохранить их в файле.
С вышестоящими терминаторами вы можете соединить эти три вверх и получить результат от Conduit как окончательный результат конвейера. Однако в большинстве случаев есть альтернативные, более простые средства для достижения тех же целей. В этом случае вы могли бы:
- использование
conduitFile
сохранить байты в файле и превратить хэш-канал в мойку хэша и поместить его вниз по течению - Используйте zipSinks для объединения как хеш-приемника, так и приемника для записи файлов в один приемник.
Классический пример того, что проще реализовать 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
-не знаю, что трубы Maybe
-осознание обратной совместимости
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
обрамляет проблемы конца ввода как подмножество синтаксического анализа.