Рекурсивный ввод-вывод в Хаскеле
В 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 []