#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 []
? Другая проблема заключается в том, что это не выполняет потоковую передачу.