Taking n lines from a file which contains m lines, repeating the file lazily if necessary
Even though module Main is implied when no header is given, I'd write it.
return . read =&lt;&lt; getLine = read &lt;$&gt; getLine = fmap read getLine is a worse version of readLn. read &lt;$&gt; getLine is basically return (error msg) when the parse fails. This passes the bottoming value into the &quot;pure&quot; part of your code. readLn badstr crashes at a well defined time—upon its execution—and if you actually wanted to handle the error you'd use Text.Read.readMaybe or readEither.
concat . repeat is cycle. Gets your point across better, doesn't it?
putStrLn . unlines is a bug. It causes an extra newline in the input: unlines creates a trailing newline and putStrLn creates another. Use putStr.
I don't hate the &quot;expression&quot; style here, but once we replace the =&lt;&lt; return . read =&lt;&lt; getLine with =&lt;&lt; readLine you may as well bring it to the front and use &gt;&gt;=, and at that point let's just use a do.
x? Really?

So:
module Main (main) where -- not my style of spacing; but fine
import System.IO (readFile)

main = do
    wanted &lt;- readLn
    contents &lt;- readFile &quot;file&quot;
    putStr $ unlines $ take wanted $ cycle $ lines contents

Though I'd also take
main = do
    wanted &lt;- readLn
    putStr . unlines . take wanted . cycle . lines =&lt;&lt; readFile &quot;file&quot;

Or, if you're attached
main = readLn &gt;&gt;= (\wanted -&gt; putStr . unlines . take wanted . cycle . lines =&lt;&lt; readFile &quot;file&quot;)

As to your concern about laziness; this is perfectly fine. readFile is lazy, and so is lines. cycle is actually better than concat . repeat; the latter keeps allocating (:)-cells as long as take consumes them, while cycle will simply connect a pointer that cycles back to the beginning of the output list, which should make it faster. Also, it errors instead of potentially looping forever on an empty input. take is of course lazy, and we've eliminated the excess laziness that would cause any parse errors to be triggered in it. Passing an infinite input (am on a *nix, so I used yes(1) and mkfifo(1)) into this program doesn't break it, which is a good litmus test for laziness.
Now, as to the functionality of this program: the wanted number should really be an argument, and so should the file (which should be optional and default to stdin). That's done with getArgs. This is complicated enough to move into new definitions. Let's get that readMaybe going, too. (Hoogle the new names to find the imports.)
dieWithUsage :: IO a
dieWithUsage = do
    name &lt;- getProgName
    die $ &quot;Usage: &quot; ++ name ++ &quot; lines [file]&quot;

parseArgs :: [String] -&gt; IO (Int, Handle)
parseArgs [] = dieWithUsage
parseArgs (wanted : rest) = (,) &lt;$&gt; getWanted &lt;*&gt; getHandle
    where getWanted = maybe dieWithUsage return $ readMaybe wanted
          getHandle = case rest of
              [] -&gt; return stdin
              [path] -&gt; openFile path ReadMode
              _ -&gt; dieWithUsage

leaving
main = do
    (wanted, input) &lt;- parseArgs =&lt;&lt; getArgs
    putStr . unlines . take wanted . cycle . lines =&lt;&lt; hGetContents input