Per the fifo(7) and pipe(7) manpages, when a fifo is opened for reading in (default) blocking mode, the open call will block until a writer also opens the fifo (and vice versa). Reading from a fifo opened in blocking mode will block until data becomes available (which will return the data) or there are no writers who have the fifo open (which will return EOF). So, if you use cat, which opens the fifo in blocking mode, it will block until the writer opens the fifo, and will read data until the writer closes the file and the cat process gets an EOF.
The Haskell readFile function, like the openFile function, opens a file in non-blocking mode. For a fifo, this means that the open call returns immediately. Reading from a fifo opened in non-blocking mode will return an EAGAIN "error" if there is no data but there are writers holding the fifo open, and it will return EOF if there are no writers holding the fifo open. When Haskell reads from the fifo (or any file), it does so using normal non-blocking I/O semantics, so if there are writers available and it gets an EAGAIN error, it knows to use poll for more data. But, if it reads from a fifo with no writers available, it gets an EOF and -- in the case of readFile -- returns an empty string.
GHC has openFileBlocking and withFileBlocking functions that you can use:
import System.IO
import GHC.IO.Handle.FD
main = openFileBlocking "/tmp/foo" ReadMode >>= hGetContents >>= putStr
This version will block on the open call and wait for the writer to join.