You can do this without any hacks.
If your goal is just to read all the stdin in String , you don't need any unsafe* functions.
IO is Monad, and Monad is an applicative functor. The functor is defined by the fmap function, whose signature is:
fmap :: Functor f => (a -> b) -> fa -> fb
which satisfies these two laws:
fmap id = id fmap (f . g) = fmap f . fmap g
Effectively fmap applies the function to wrapped values.
For a specific character 'c' , what is the type of fmap ('c':) ? We can write two types down and then combine them:
fmap :: Functor f => (a -> b ) -> fa -> fb ('c':) :: [Char] -> [Char] fmap ('c':) :: Functor f => ([Char] -> [Char]) -> f [Char] -> f [Char]
Recalling that IO is a functor, if we want to define myGetContents :: IO [Char] , it seems reasonable to use this:
myGetContents :: IO [Char] myGetContents = do x <- getChar fmap (x:) myGetContents
This is close, but not quite equivalent to getContents , as this version will try to read past the end of the file and throw an error instead of returning a line. Just by looking at it, you need to make it clear: there is no way to return a specific list, only an endless chain of cons. Knowing that the specific case "" in EOF (and using the infix <$> syntax for fmap ) leads us to:
import System.IO myGetContents :: IO [Char] myGetContents = do reachedEOF <- isEOF if reachedEOF then return [] else do x <- getChar (x:) <$> myGetContents
The applicative class provides (slight) simplification.
Recall that IO is an applicative functor, not some old functor. There are “Applicable laws” associated with this type, as well as “Functor-laws”, but we will look specifically at <*> :
<*> :: Applicative f => f (a -> b) -> fa -> fb
This is almost identical to fmap (aka <$> ), except that the function used is also wrapped. Then we can avoid snapping in our else clause using the applicative style:
import System.IO myGetContents :: IO String myGetContents = do reachedEOF <- isEOF if reachedEOF then return [] else (:) <$> getChar <*> myGetContents
One modification is required if the input can be infinite.
Remember when I said you don't need unsafe* functions if you just want to read all stdin in String ? Well, if you just want to contribute, you will. If your entry can be infinitely long, you will definitely do it. The final program is distinguished by one import and one word:
import System.IO import System.IO.Unsafe myGetContents :: IO [Char] myGetContents = do reachedEOF <- isEOF if reachedEOF then return [] else (:) <$> getChar <*> unsafeInterleaveIO myGetContents
The defining function of the lazy IO is unsafeInterleaveIO (from System.IO.Unsafe ). This delays the calculation of the IO action until it is needed.