2017-02-11 6 views
1

Я понимаю, что я не должен пытаться перечитываю из stdin из-за ошибок о Haskell IO - handle closed Например, в поле ниже:Как эффективно повторно использовать входные данные из стандартного ввода в Haskell

main = do 
    x <- getContents 
    putStrLn $ map id x 
    x <- getContents  --problem line 
    putStrLn x 

второй вызов x <- getContents вызовет ошибка:

test: <stdin>: hGetContents: illegal operation (handle is closed) 

конечно, я могу опустить вторую линию для чтения из getContents.

main = do 
    x <- getContents 
    putStrLn $ map id x 
    putStrLn x 

Но будет ли это проблемой производительности/памяти? Будет ли GHC хранить все содержимое с stdin в основной памяти?

Я предполагаю, что в первый раз, когда потребляется x, GHC может выбросить порты x, которые уже обработаны. Поэтому теоретически GHC может использовать только небольшую часть постоянной памяти для обработки. Но так как мы снова будем использовать x (и снова), похоже, что GHC ничего не может выбросить. (И он не может читать снова с stdin).

Мое понимание влияния памяти здесь правильно? И если да, есть ли исправление?

+0

Да, он должен хранить все stdin в памяти. Я не вижу, как будет возможно исправление: хотите ли вы, чтобы ваша программа удаляла данные и все еще имела ее позже? Возможно, вы ищете что-то вроде 'do x <- getContents; y <- useMyDataSomehow x; useMoreData y'? В любом случае ваша проблема может быть решена библиотеками каналов/каналов (за счет написания вашей программы в стиле, требуемом этими библиотеками) – chi

+0

У вас это точно верно 'do {cs <- getContents; putStrLn cs} 'использует в основном память,' do {cs <- getContents; putStrLn cs; putStrLn cs} 'накапливает все' cs' в первый раз. Как вы обойдетесь, это будет зависеть от того, что вы делаете.Вы буквально хотите «putStrLn» дважды? Или, например, длину записи и печать, или что? – Michael

+0

Грубо говоря, я хочу использовать Haskell для реализации UNIX 'tee' или его варианта. – tinlyx

ответ

2

Да, ваше понимание верное: если вы повторно используете x, ghc должен хранить все это в памяти.

Я думаю, что возможное решение - использовать его лениво (один раз).

Предположим, вы хотите вывести x нескольким выходным ручкам hdls :: [Handle]. Наивный подход:

main :: IO() 
main = do 
    x <- getContents 
    forM_ hdls $ \hdl -> do 
     hPutStr hdl x 

Это будет читать stdin в x как первый hPutStr пересекает строку (по крайней мере, для небуферных ручек, hPutStr просто цикл, который вызывает hPutChar для каждого символа в строке). С этого момента он будет храниться в памяти для всех следующих hdl s.

В качестве альтернативы:

main :: IO() 
main = do 
    x <- getContents 
    forM_ x $ \c -> do 
     forM_ hdls $ \hdl -> do 
      hPutChar hdl c 

Здесь мы транспонированные петли: Вместо итерации ручки (и для каждой ручки итерации входных символов), мы перебирать входные символы, и для каждого символа , мы печатаем его каждому дескриптору.

Я не тестировал его, но эта форма должна гарантировать, что нам не нужно много памяти, потому что каждый входной символ c используется один раз, а затем отбрасывается.

+0

Спасибо. Я вижу, как работает ваш пример. Но мне, наверное, нужно что-то более сложное, чем синхронизированные обработчики. Например, если мне нужно «отменить» или «сортировать» поток во втором раунде? Я предполагаю, что где-то по линии, он сломается. – tinlyx

+0

@tinlyx 'reverse' и' sort' будут вынуждать все входные данные накапливаться, это не потоковые операции с текстовыми книгами. Такие вещи, как 'length' или количество слов и т. Д., Будут работать. – Michael

+0

Спасибо за разъяснение. Как вы думаете, если я использую, например, 'reverse' в большом потоке, я получаю такую ​​же проблему памяти в любом случае, независимо от того, используется ли она во втором раунде? – tinlyx

 Смежные вопросы

  • Нет связанных вопросов^_^