haskelllazy-io

Reimplementing getContents using getChar


On my journing towards grasping lazy IO in Haskell I tried the following:

main = do
  chars <- getContents
  consume chars

consume :: [Char] -> IO ()
consume [] = return ()
consume ('x':_) = consume []
consume (c : rest) = do
  putChar c
  consume rest

which just echos all characters typed in stdin until I hit 'x'.

So, I naively thought it should be possible to reimplement getContents using getChar doing something along the following lines:

myGetContents :: IO [Char]
myGetContents = do
  c <- getChar
  -- And now?
  return (c: ???) 

Turns out it's not so simple since the ??? would require a function of type IO [Char] -> [Char] which would - I think - break the whole idea of the IO monad.

Checking the implementation of getContents (or rather hGetContents) reveals a whole sausage factory of dirty IO stuff. Is my assumption correct that myGetContents cannot be implemented without using dirty, ie monad-breaking, code?


Solution

  • You need a new primitive unsafeInterleaveIO :: IO a -> IO a that delays the execution of its argument action until the result of that action would be evaluated. Then

    myGetContents :: IO [Char]
    myGetContents = do
      c <- getChar
      rest <- unsafeInterleaveIO myGetContents
      return (c : rest)