В какой-то момент я написал программу захвата пакетов на 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
И теперь это работает. Большое спасибо, я боялся, что не получу ответ.