Какова реальная выгода от параметра типа восходящего канала?

Я пытаюсь понять различия между различными реализациями концепции труб. Одно из различий между трубопроводом и трубами заключается в том, как они соединяют трубы вместе. Трубопровод имеет

(>+>) :: 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 как окончательный результат конвейера. Однако в большинстве случаев есть альтернативные, более простые средства для достижения тех же целей. В этом случае вы могли бы:

  1. использование conduitFile сохранить байты в файле и превратить хэш-канал в мойку хэша и поместить его вниз по течению
  2. Используйте 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 обрамляет проблемы конца ввода как подмножество синтаксического анализа.

Другие вопросы по тегам