Сворачивание подмножества потока с использованием каналов 4.0

Я пытаюсь понять каналы 4.0 и хочу преобразовать код канала. Предположим, у меня есть поток Ints, и я хотел бы пропустить первые пять, а затем получить сумму следующих 5. Используя простые списки, это будет:

sum . take 5 . drop 5

В проводнике это будет:

drop 5
isolate 5 =$ fold (+) 0

Или как полную программу:

import Data.Conduit
import Data.Conduit.List (drop, isolate, fold)
import Prelude hiding (drop)

main :: IO ()
main = do
    res <- mapM_ yield [1..20] $$ do
        drop 5
        isolate 5 =$ fold (+) 0
    print res

Однако я не совсем уверен, как это сделать с трубами.


person Michael Snoyman    schedule 24.09.2013    source источник
comment
Но Пайпс имеет то же самое: брать, бросать и складывать.   -  person Sassa NF    schedule 24.09.2013
comment
@SassaNF Тип fold в каналах значительно отличается от типа в каналах, из-за чего и возникает путаница.   -  person Michael Snoyman    schedule 25.09.2013


Ответы (2)


Раньше я не использовал Pipes, но после изучения руководства понял, что это очень просто:

import Pipes
import qualified Pipes.Prelude as P

nums :: Producer Int IO ()
nums = each [1..20]

process :: Producer Int IO ()
process = nums >-> (P.drop 5) >-> (P.take 5)

result :: IO Int
result = P.fold (+) 0 id process

main = result >>= print

ОБНОВЛЕНИЕ:

Поскольку в примере нет «эффективной» обработки, мы можем даже использовать монаду Identity в качестве базовой монады для канала:

import Pipes
import qualified Pipes.Prelude as P
import Control.Monad.Identity

nums :: Producer Int Identity ()
nums = each [1..20]

process :: Producer Int Identity ()
process = nums >-> (P.drop 5) >-> (P.take 5)

result :: Identity Int
result = P.fold (+) 0 id process

main = print $ runIdentity result

ОБНОВЛЕНИЕ 1:

Ниже приведено решение, которое я придумал (для комментария к основной ссылке), но я чувствую, что его можно сделать более элегантным.

fun :: Pipe Int (Int, Int) Identity ()
fun = do
  replicateM_ 5 await
  a <- replicateM 5 await
  replicateM_ 5 await
  b <- replicateM 5 await
  yield (sum a, sum b)

main = f $ runIdentity $ P.head $ nums >-> fun where
  f (Just (a,b)) = print (a,b)
  f Nothing = print "Not enough data"
person Ankur    schedule 24.09.2013
comment
Спасибо, это то, что я искал. Я пробовал что-то похожее на то, что не сработало, хотя я не могу найти, что это было. - person Michael Snoyman; 24.09.2013
comment
На самом деле, я думаю, что в итоге я действительно запутался в этом немного более сложном примере: gist.github .com/snoyberg/6683033, но это отдельная тема. Если у вас есть какие-либо идеи по этому поводу, дайте мне знать, иначе я просто открою это как отдельный вопрос. - person Michael Snoyman; 24.09.2013
comment
Обратите внимание, что вы также можете использовать канал sum, но это все равно очень хороший ответ. - person Gabriel Gonzalez; 24.09.2013
comment
Я понимаю, что этот ответ дает тот же результат, но он сильно отличается от сути. (1) Он считывает значения в память вместо потоковой свертки. (2) В случае sum это работает нормально, но если бы потребителем было что-то более сложное (например, zlib-сжатие и сохранение в файл), мы не могли бы просто получить функцию Prelude. (3) Он ведет себя по-другому, если ввода недостаточно. - person Michael Snoyman; 24.09.2013

Чтобы ответить на ваш комментарий, это все еще работает в общем случае. Я также разместил тот же ответ на Reddit, где вы также задали аналогичный вопрос, но я дублирую ответ здесь:

import Pipes
import Pipes.Parse
import qualified Pipes.Prelude as P

main :: IO ()
main = do
    res <- (`evalStateT` (each [1..20])) $ do
        runEffect $ for (input >-> P.take 5) discard
        P.sum (input >-> P.take 5)
    print res

Это будет обобщено на более сложные случаи, которые вы имели в виду.

person Gabriel Gonzalez    schedule 24.09.2013