• Что такое трубы/проводника, пытаясь решить

    Я видел, что люди рекомендуют использовать трубы/библиотеки проводника для различных ленивый, связанных с ИО задачи. Какая проблема сделать эти библиотеки точно решить?

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

    Меня это сбивает с толку. Для моих задач синтаксического анализа следует использовать attoparsec или труб-attoparsec/attoparsec-проводника? Какая выгода делать трубы/проводника версию дайте мне по сравнению с простой ванили attoparsec?

  • Ответы

  • Ленивый ИО

    Ленивый ИО работает как это

    readFile :: FilePath -> IO ByteString 

    где ByteString гарантированно будет читать только кусок-на-кусок. Чтобы сделать это, мы могли бы (почти) писать

    -- given `readChunk` which reads a chunk beginning at n readChunk :: FilePath -> Int -> IO (Int, ByteString)  readFile fp = readChunks 0 where   readChunks n = do     (n', chunk) <- readChunk fp n     chunks      <- readChunks n'     return (chunk <> chunks) 

    но здесь отметим, что IO действий readChunks n' выполняется до возвращения даже частичный результат доступен chunk. Это значит, что мы не ленивые вовсе. Для борьбы с этим мы используем unsafeInterleaveIO

    readFile fp = readChunks 0 where   readChunks n = do     (n', chunk) <- readChunk fp n     chunks      <- unsafeInterleaveIO (readChunks n')     return (chunk <> chunks) 

    что вызывает readChunks n' немедленно вернуться, преобразование в IO действие будет выполнено только тогда, когда что подумать заставляют.

    Это опасная часть: с помощью unsafeInterleaveIO мы уже затянули кучу IO действия в неопределенное время в будущем, которые зависят от того, как мы потребляем наши куски ByteString.

    Устранение проблемы с сопрограмм

    То, что мы хотели бы сделать, это положить кусок шаг обработки в между вызовом readChunk и рекурсия на readChunks.

    readFileCo :: Monoid a => FilePath -> (ByteString -> IO a) -> IO a readFileCo fp action = readChunks 0 where   readChunks n = do     (n', chunk) <- readChunk fp n     a           <- action chunk     as          <- readChunks n' action     return (a <> as) 

    Теперь у нас есть возможность выполнять произвольные IO действия после каждой небольшой фрагмент загружается. Это позволяет нам делать гораздо больше работы, постепенно, без полной загрузки ByteString в память. К сожалению, это не жутко композиционные--мы должны строить наше потребление action и передаем его нашим ByteString продюсер для того, чтобы работать.

    Трубы на основе ИО

    Это по сути то, что pipes решает-он позволяет нам составить effectful со-процедуры с легкостью. Например, сейчас мы пишем наш файл ридер, как Producer который может рассматриваться как "потоковое" куски файла, когда его воздействие получает разбежаться окончательно.

    produceFile :: FilePath -> Producer ByteString IO () produceFile fp = produce 0 where   produce n = do     (n', chunk) <- liftIO (readChunk fp n)     yield chunk     produce n' 

    Обратите внимание на сходство между этим кодом и readFileCo выше—мы просто заменим вызова сопрограмм действий с yieldную chunk до сих пор у нас производят. Этот призыв к yield строит Producer типа вместо сырых IO действие, которое мы можем составить с другими Pipeтипы S для того чтобы построить хороший конвейера потребления называется Effect IO ().

    Все это дом труба делается статически, без фактического вызова любой из IO действия. Это как pipes позволяет легко писать свои сопрограмм. Все эффекты срабатывать сразу, когда мы называем runEffect в нашем main IO действий.

    runEffect :: Effect IO () -> IO () 

    Attoparsec

    Так зачем вы хотите подключить attoparsec в pipes? Ну, attoparsec оптимизирован для ленивых разбора. Если вы производите блоки подают в attoparsec парсер в effectful путь, то вы будете в тупике. Вы могли бы

    1. Использовать строгие IO и загрузить всю строку в памяти только потреблять его лениво с вашего парсера. Это простой, предсказуемый, но неэффективно.
    2. Используйте Lazy IO и теряют способность рассуждать о том, когда Ваше производство ИО эффектов на самом деле получаете вызывает возможной утечки ресурсов или закрыты обрабатывать исключения по графику расход вашего разбираемых элементов. Это более эффективно, чем (1), но может легко стать непредсказуемо; или,
    3. Использовать pipes (или conduit), чтобы выстроить систему сопрограмм, которые включают в себя ваши ленивые attoparsec парсер, позволяющий ему функционировать в качестве маленький вход, а он должен при производстве проанализированные значения максимально лениво через весь поток.

    Если вы хотите использовать attoparsec, используйте attoparsec

    Для моих задач синтаксического анализа следует использовать attoparsec или труб-attoparsec/attoparsec-проводника?

    Как pipes-attoparsec и attoparsec-conduit преобразование данного attoparsec Parser в раковину/канала или трубы. Поэтому вы должны использовать attoparsec в любом случае.

    Какая выгода делать трубы/проводника версию дайте мне по сравнению с простой ванили attoparsec?

    Они работают с трубами и каналом, где ваниль не будет (по крайней мере не из-из-коробки).

    Если Вы не используете проводник или труб, а вас устраивает текущая производительность вашей ленивый ИО, нет необходимости менять свой ток, особенно если Вы не пишете приложение для больших или обрабатывать большие файлы. Вы можете просто использовать attoparsec.

    Однако, это предполагает, что вы знаете недостатки ленивый ИО.

    Что случилось с ленивым ИО? (Исследование проблемы withFile)

    Давайте не будем забывать свой первый вопрос:

    Какая проблема сделать эти библиотеки точно решить ?

    Они решают потоковых данных проблем (см. 1 и 3), Что происходит в функциональных языках с ленивым ИО. Ленивый ИО иногда дает не то, что вы хотите (см. пример ниже), и иногда трудно определить фактическое системных ресурсов, необходимых конкретному ленивый операции (это записи/чтения данных блоками/байт/буферизованный/onclose с/onopen...).

    Пример более-лень

    import System.IO main = withFile "myfile" ReadMode hGetContents        >>= return . (take 5)        >>= putStrLn 

    Это не будет ничего печатать, поскольку оценка данных происходит в putStrLn но ручки уже закрыт на этот момент.

    Фиксируя огонь с ядовитой кислотой

    В то время как в следующем фрагменте исправления этого, он имеет еще одну неприятную особенность:

    main = withFile "myfile" ReadMode $ \handle ->             hGetContents handle        >>= return . (take 5)        >>= putStrLn 

    В этом случае hGetContents будет читать весь файл, что-то Вы не ожидали поначалу. Если вы просто хотите, чтобы проверить волшебную байт файла, который может быть несколько ГБ, это не путь, чтобы пойти.

    Используя withFile правильно

    Решение, очевидно, take вещи в withFile контекст:

    main = withFile "myfile" ReadMode $ \handle ->             fmap (take 5) (hGetContents handle)        >>= putStrLn 

    Это, кстати, также решение упомянутых автором труб:

    Это [..] отвечает на вопрос люди иногда спрашивают меня о pipes, который я буду paraphase здесь:

    Если ресурс управления не является одним из основных направлений pipes почему я должен использовать pipes вместо того, чтобы ленивый ИО?

    Многие люди задают этот вопрос обнаружен программирования, трансляция через Олега, кто подставил ленивых проблема ввода-вывода с точки зрения управления ресурсами. Однако я не счел этот довод убедительным в изоляции; вы можете решить большинство ресурсов управления вопросы, просто разделив получение ресурса от ленивых ввода-вывода, как это: [см. последний пример выше]

    Что возвращает нас к моему предыдущему выступлению:

    Вы можете просто использовать attoparsec [...][с ленивым ИО, при условии], что вы знаете недостатки ленивый ИО.

    Ссылки

    Вот отличный подкаст с авторами обеих библиотек:

    http://www.haskellcast.com/episode/006-gabriel-gonzalez-and-michael-snoyman-on-pipes-and-conduit/

    Она ответит на большинство ваших вопросов.


    Короче, обе эти библиотеки подойти к проблеме эфире, что очень важно при работе с IO. По сути они управляют передачей данных в куски, таким образом позволяя вам, например, для передачи файла 1 Гб cosuming всего 64 КБ оперативной памяти на сервере и клиенте. Без потоковой передачи, вы должны были бы выделить столько памяти на обоих концах.

    Старая альтернатива для тех библиотек ленивый ИО, но она наполнена проблемами и делает ошибкам приложений. Эти вопросы обсуждаются в подкасте.

    В отношении которых один из этих библиотек, чтобы использовать, это скорее дело вкуса. Я предпочитаю "трубы". Подробные отличия тоже обсуждали в подкасте.