Рекурсивный ввод-вывод в Хаскеле

В 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

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

3 ответа

Решение

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

Вы все еще должны использовать do-notation, что приведет к этому:

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.

Если вы проверите тип (++) в 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),

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

if readable
    then return hGetLine h ++ hGetLines h
    else return []
Другие вопросы по тегам