Возникли проблемы с завершением этого перечисления

В какой-то момент я написал программу захвата пакетов на Haskell, и она использовала ленивый ввод-вывод для перехвата всех TCP-пакетов. Проблема заключалась в том, что иногда пакеты идут не по порядку, поэтому мне приходилось вставлять их все в список, пока я не получил флаг fin, чтобы быть уверенным, что у меня есть все пакеты, необходимые для того, чтобы с ними что-то делать, и если я что-то нюхал очень большой, как видео, я должен был держать все это в памяти. Чтобы сделать это любым другим способом, потребуется сложный императивный код.

Так что позже я узнал об итерациях и решил реализовать свои собственные. Как это будет работать, есть перечисление. Вы предоставляете ему количество пакетов, которые хотите хранить. Когда он получает пакеты, он сортирует их, а затем, когда он достигает указанного вами числа, он начинает сбрасывать, но оставляет несколько там, чтобы новые фрагменты сортировались в этом списке до того, как будут сброшены другие пакеты. Идея состоит в том, что куски будут почти в порядке, прежде чем они попадут в это перечисление, и это устранит большинство проблем с небольшим порядком. Когда он получает EOF, он должен отправить все оставшиеся пакеты обратно.

Так что это почти работает. Я понимаю, что некоторые из них можно было бы заменить стандартными функциями перечислителя, но я хотел написать их сам, чтобы лучше понять, как это работает. Вот код:

Readlines просто получает строки из файла по одной строке за раз и передает их. PrintLines просто печатает каждый фрагмент. Numbers.txt представляет собой набор чисел с разделителями строк, которые немного не в порядке, некоторые числа представляют собой несколько пробелов до или после того, как они должны быть. Переупорядочивание — это функция, которая хранит n чисел и сортирует новые в свой список-накопитель, а затем выталкивает все, кроме последних n из этих чисел.

import Prelude as P
import Data.Enumerator as E
import Data.Enumerator.List as EL
import Data.List (sort, insert)
import IO
import Control.Monad.Trans (lift)
import Control.Monad (liftM)

import Control.Exception as Exc
import Debug.Trace

test = run_ (readLines "numbers.txt" $$ EL.map (read ::String -> Int) =$ reorder 10 =$ printLines)

reorder :: (Show a, Ord a) => (Monad m) => Int -> Enumeratee a a m b
reorder n step = reorder' [] n step
  where
    reorder' acc n (Continue k) =
      let
        len = P.length
        loop buf n' (Chunks xs)
          | (n' - len xs >= 0) = continue (loop (foldr insert buf xs) (n' - len xs))
          | otherwise  =
              let allchunx = foldr insert buf xs
                  (excess,store)= P.splitAt (negate (n' - len xs)) allchunx
              in k (Chunks excess) >>== reorder' store 0
        loop buf n' (EOF) = k (Chunks (trace ("buf:" ++ show buf) buf)) >>== undefined
      in continue (loop acc n)

printLines :: (Show a) => Iteratee a IO ()
printLines = continue loop
  where
    loop (Chunks []) = printLines
    loop (Chunks (x:xs)) = do
      lift $ print x
      printLines
    loop (EOF) = yield () EOF

readLines :: FilePath -> Enumerator String IO ()
readLines filename s = do
  h <- tryIO $ openFile filename ReadMode
  Iteratee (Exc.finally (runIteratee $ checkContinue0 (blah h) s) (hClose h))
  where
    blah h loop k = do
      x <- lift $ myGetLine h
      case x of
        Nothing -> continue k
        Just line -> k (Chunks [line]) >>== loop
    myGetLine h = Exc.catch (liftM Just (hGetLine h)) checkError
    checkError :: IOException -> IO (Maybe String)
    checkError e = return Nothing

Моя проблема в неопределенном порядке. Что происходит, так это то, что в reorder застряли 10 элементов, а затем он получает EOF из стека. Итак, это k (Разбивает эти 10 элементов на куски), а затем есть неопределенное, потому что я не знаю, что здесь поставить, чтобы это заработало.

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

Изменить: оказалось, что изменение порядка было исправлено путем изменения неопределенной части цикла на:

loop buf n' EOF = k (Chunks buf) >>== (\s -> yield s EOF)

который у меня почти наверняка был в какой-то момент, но я не получил правильного ответа, поэтому я предположил, что это неправильно.

Проблема была с printLines. Поскольку reorder отправлял куски по одному, пока не доходил до самого конца, я никогда не замечал проблемы с printLines, которая заключалась в том, что он отбрасывал куски, отличные от первого, за цикл. В моей голове я думал, что куски будут переноситься или что-то в этом роде, что было глупо.

В любом случае я изменил printLines на это:

printLines :: (Show a) => Iteratee a IO ()
printLines = continue loop
  where
    loop (Chunks []) = printLines
    loop (Chunks xs) = do
      lift $ mapM_ print xs
      printLines
    loop (EOF) = yield () EOF

И теперь это работает. Большое спасибо, я боялся, что не получу ответ.


person David McHealy    schedule 21.04.2011    source источник


Ответы (1)


Как насчет

loop buf n' (EOF) = k (Chunks buf) >>== (\s -> yield s EOF)

(идея взята из EB.isolate).

В зависимости от того, что именно вы пытаетесь сделать, ваши линии печати также могут нуждаться в исправлении; случай для кусков (x:xs) отбрасывает xs. Что-то типа

loop (Chunks (x:xs)) = do
  lift $ print x
  loop (Chunks xs)

может быть (а может и не быть) тем, что вы хотели.

person wagstaff    schedule 21.04.2011
comment
Оказывается, ты был прав. У меня действительно было это, но когда он дал неправильный ответ, я предположил, что проблема связана со сложной функцией. - person David McHealy; 22.04.2011