Рекурсивный ввод-вывод в Haskell

#haskell #recursion #io #monads #lazy-evaluation

#haskell #рекурсия #io #монады #отложенная оценка

Вопрос:

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

 Prelude> let countdown i = if (i > 0) then (show i)    countdown (i-1) else ""
Prelude> countdown 5
"54321"
  

Я хочу использовать такой же дизайн для чтения доступных данных из дескриптора файла. В данном конкретном случае мне нужно прочитать данные тем же способом, что и hGetContents, но не оставляя дескриптор в «полузакрытом» состоянии, чтобы я мог циклически взаимодействовать с дескрипторами stdin / stdout процесса, открытого с помощью CreateProcess:

 main = do
    -- do work to get hin / hout handles for subprocess input / output

    hPutStrLn hin "whats up?"

    -- works
    -- putStrLn =<< hGetContents hout

    putStrLn =<< hGetLines hout

    where
        hGetLines h = do
            readable <- hIsReadable h
            if readable
                then hGetLine h    hGetLines h
                else []
  

Выдает ошибку:

 Couldn't match expected type `IO b0' with actual type `[a0]'
In the expression: hGetLine h : hGetLines h
  

Я знаю, что существуют различные библиотеки, доступные для выполнения того, что я пытаюсь выполнить, но, как я понимаю, мой вопрос на самом деле заключается в том, как выполнить рекурсивный ввод-вывод. ТИА!

Ответ №1:

Наивное решение, строгий стек и O (n)

Вам все равно придется использовать do-нотацию, которая привела бы к этому:

 import System.IO
import System.IO.Unsafe (unsafeInterleaveIO)

-- Too strict!
hGetLines :: Handle -> IO [String]
hGetLines h = do
    readable <- hIsReadable h
    if readable
        then do
            x  <- hGetLine h
            xs <- hGetLines h
            return (x:xs)
        else return []
  

Но смотрите мой комментарий, эта версия hGetLines слишком строгая!

Ленивая потоковая версия

Он не вернет ваш список, пока не получит все входные данные. Вам нужно что-то более ленивое. Для этого у нас есть unsafeInterleaveIO ,

 -- Just right
hGetLines' :: Handle -> IO [String]
hGetLines' h = unsafeInterleaveIO $ do
    readable <- hIsReadable h
    if readable
        then do
            x  <- hGetLine h
            xs <- hGetLines' h
            return (x:xs)
        else return []
  

Теперь вы можете начать потоковую передачу результатов построчно в свой потребительский код:

 *Main> hGetLines' stdin
123
["123"345
,"345"321
,"321"^D^CInterrupted.
  

Ответ №2:

Если вы проверите тип ( ) в ghci, вы получите:

 Prelude> :t (  )
(  ) :: [a] -> [a] -> [a]
  

Это означает, что вы можете добавлять списки только вместе (помните, что это String псевдоним для [Char] , так что это список). Тип hGetLine является Handle -> IO String , и тип hGetLines должен быть IO [String] таким, чтобы вы не могли добавлять эти значения. (:) имеет тип a -> [a] и работает здесь лучше.

 if readable
  then do
    -- First you need to extract them
    a <- hGetLine h
    b <- hGetLines h
    -- a and b have type String
    -- Now we can cons them and then go back into IO
    return (a : b)
  

То же самое относится к else [] . Вам нужно возвращаемое значение типа IO [String] . Измените его на return []

Кроме того, вы не сможете использовать только putStrLn строки, так как (=<< hGetLines h) выдает вам [String] , а не String то, что putStrLn ожидается. Это можно решить несколькими способами. Сначала нужно объединить значения. putStrln . concat =<< (hGetLines h) . Или вы можете напечатать каждую строку с помощью mapM_ putStrLn (hGetLines h) .

Комментарии:

1. Вы имели в виду вызвать hGetLines во втором вызове там?

2. Упс. Пропустил рекурсивный вызов, поэтому : следует использовать вместо него.

3. Обратите внимание, что этот пример не будет потоковым и использует O (n) стека.

Ответ №3:

Это говорит о том, что часть кода ожидает, что hGetLines h будет иметь тип IO a , а другая часть считает, что он имеет тип [a] . Вы, вероятно, хотите, чтобы ваш оператор if был:

 if readable
    then return hGetLine h    hGetLines h
    else return []
  

Комментарии:

1. Ваш код несколько странный… Он даже не компилируется. Как насчет этого: if readable then hGetLine >>= a -> hGetLine >>= b -> return $ a b else return [] ? Другая проблема заключается в том, что это не выполняет потоковую передачу.