Рекурсивный ввод-вывод в Хаскеле - PullRequest
5 голосов
/ 24 мая 2011

В 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 ]

10 голосов
/ 24 мая 2011

Наивное решение, строгий и 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.
6 голосов
/ 24 мая 2011

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

0 голосов
/ 24 мая 2011

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

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