Conduit: извлечение последовательных чисел

Я играю с библиотекой каналов и написал пример кода для извлечения двух чисел (2 и 3), когда они появляются в последовательности. Ниже приведен мой код:

import Data.Conduit
import qualified Data.Conduit.List as CL

source = CL.sourceList [1,2,3,4,5,2,3] :: Source IO Int

-- Extract the consequent 2 and 3 number
extract23 :: Conduit Int IO Int
extract23 = do
  a <- await
  b <- await
  case (a,b) of
    (Just a,Just b) ->
      if a == 2 && b == 3
      then do yield a
              yield b
              extract23
      else extract23
    _ -> return ()

conduit1 :: Conduit Int IO String
conduit1 = CL.map show

sink1 :: Sink String IO ()
sink1 = CL.mapM_ putStrLn

main :: IO ()
main = source $= (extract23 =$= conduit1) $$ sink1

Но когда я выполняю функцию main, я не получаю вывода. На самом деле я ожидаю что-то вроде этого:

2
3
2
3

Любая идея о том, что я делаю неправильно?


person Sibi    schedule 13.02.2014    source источник


Ответы (1)


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

[(1,2),(3,4),(5,2)] -- final 3 is lost since it has no pair

Проблема в том, что последовательности 2,3 всегда попадают между двумя из этих кортежей. Мне кажется, что алгоритм, который вы действительно хотите, это:

  • Проверьте, совпадают ли первые два значения в потоке с 2,3.
  • Продвиньтесь вперед в потоке на один элемент и повторите.

В настоящее время вы продвигаетесь вперед на два элемента в потоке.

К счастью, у этой проблемы есть простое решение: вместо использования await для получения второго значения, которое одновременно удаляет его из потока, используйте peek, которое просматривает значение и возвращает его. Если вы замените b <- await на b <- CL.peek, вы должны получить поведение, которое ищете.

ОБНОВЛЕНИЕ

Просто чтобы дать немного больше информации. Под поверхностью peek реализуется поверх двух примитивов в канале: await и leftover, вот так:

peek = do
    mx <- await
    case mx of
        Nothing -> return Nothing
        Just x -> do
            leftover x
            return (Just x)

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

peek2 = do
    mx <- await
    my <- await
    maybe (return ()) leftover my
    maybe (return ()) leftover mx
    return (mx, my)
person Michael Snoyman    schedule 13.02.2014
comment
Спасибо, это решило проблему. Но что, если я хочу видеть в потоке и третий элемент? Как обращаться в таком случае? peek в этом случае не работает. - person Sibi; 13.02.2014