Small haskell text-processing script
I found out that using Data.ByteString.Lazy.Char8 could significantly improve the running time (about one third of the one I posted initially).

Here is the code, taking into account some suggestions by Gurkenglas:

import qualified Data.ByteString.Lazy.Char8 as C

-- Define type synonyms for fields in a fastq record.
type Name = C.ByteString
type Nucleotides = C.ByteString
type Qualities = C.ByteString

data Fastq = Fastq Name Nucleotides Qualities

formatFastq :: Fastq -&gt; C.ByteString
formatFastq (Fastq n s q) = C.unlines [n, s, C.pack "+", q]

-- Parse lines four by four, ignore the third of each batch.
getFastqs :: [C.ByteString] -&gt; [Fastq]
getFastqs [] = []
getFastqs (l1 : l2 : _ : l4 : ls) = Fastq l1 l2 l4 : getFastqs ls

{-
We assume that the records come in sorted by sequence.
Successive records having the same sequence are fused,
arbitrarilly keeping the first name, and taking the
highest quality at each position.
-}
fuseFastqs :: [Fastq] -&gt; [Fastq]
fuseFastqs [] = []
fuseFastqs [r] = [r]
fuseFastqs (r1@(Fastq n s1 q1) : r2@(Fastq _ s2 q2) : rs)
    | s1 == s2 = fuseFastqs (Fastq n s1 (bestquals q1 q2) : rs)
    | otherwise = r1 : fuseFastqs (r2 : rs)

{-
For each position, we want to keep the highest quality
among the records that had the same sequence.
In fastq format, qualities are integers represented as ascii characters
Using max on Char does what we want, and seems to work on Bytestring elements.
-}
bestquals :: Qualities -&gt; Qualities -&gt; Qualities
bestquals q1 q2 = C.pack (C.zipWith max q1 q2)

processLines :: [C.ByteString] -&gt; [C.ByteString]
processLines ls = map formatFastq (fuseFastqs (getFastqs ls))

main :: IO ()
main = C.interact (C.concat . processLines . C.lines)


It would be nice to be able to avoid the ugly C.pack in bestquals.