Reputation: 4110
In Haskell I can easily define a recursive function which takes a value and returns a string:
Prelude> let countdown i = if (i > 0) then (show i) ++ countdown (i-1) else ""
Prelude> countdown 5
"54321"
I want to use the same kind of design to read available data from a file handle. In this particular case I need to read the data in the same fashion as hGetContents, but without leaving the handle in the "semi-closed" state, so that I can loop interaction with stdin/stdout handles of a process opened with 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 []
Gives the error:
Couldn't match expected type `IO b0' with actual type `[a0]'
In the expression: hGetLine h : hGetLines h
I know there are various libraries available for accomplishing what I'm trying to accomplish, but sice I'm learning my question is really how to perform recursive IO. TIA!
Upvotes: 5
Views: 4870
Reputation: 137937
Naive solution, strict and O(n) stack
You still have to use the do-notation, which would lead to this:
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 []
But see my comment, this version of hGetLines
is too strict!
Lazy, streaming version
It won't return your list, until it has all the input. You need something a bit lazier. For this, we have 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 []
Now you can start streaming results line-by-line to your consumer code:
*Main> hGetLines' stdin
123
["123"345
,"345"321
,"321"^D^CInterrupted.
Upvotes: 12
Reputation: 7536
If you check the type of (++)
in ghci you get:
Prelude> :t (++)
(++) :: [a] -> [a] -> [a]
Meaning you can only append lists together (Remember that String
is an alias for [Char]
, so it's a list). The type of hGetLine is Handle -> IO String
, and the type of hGetLines
should be IO [String]
So you can not append these values. (:)
has type a -> [a]
and works better here.
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)
The same applies for else []
. You need a value of type IO [String]
to be returned. Change it to return []
Also, you won't be able to just putStrLn
the lines since (=<< hGetLines h)
gives you [String]
and not String
which is what putStrLn
expects.
This can be solved in several ways. One is to concat the values first. putStrln . concat =<< (hGetLines h)
. Or you can print each line using mapM_ putStrLn (hGetLines h)
.
Upvotes: 6
Reputation: 5415
This is saying that part of the code expects hGetLines h to have type IO a
and another part finds it to have type [a]
. You probably want your if statement to be:
if readable
then return hGetLine h ++ hGetLines h
else return []
Upvotes: -1