Improve Trie manipulation in Haskell code
Can you add minimal imports so your code compiles? I had to guess:

import Data.ListTrie.Map as M hiding (null)
import qualified Data.Map as Map
import Data.Maybe
import Data.List


data Event = Event deriving (Show, Eq, Ord)
data Result = Result deriving (Show, Eq)


constructTrie can be easily improved by inlining comp:

constructTrie list = M.fromListWith (++) list


Or even to constructTrie = M.fromListWith (++) but that requires turning off monomorphism restriction or adding an explicit signature for constructTrie, so it's up to your taste.

Also you can deduplicate showTrie calls and long trie signatures:

mshow list = M.showTrie list ""

type ResultTrie = M.TrieMap Map.Map Event [Result]

duplicates :: ResultTrie -&gt; String
duplicates m = mshow list
  where
    list = M.filter (\v -&gt; length v /= 1) m

prefixOverlap :: ResultTrie -&gt; String
prefixOverlap m = mshow list
  where
    list = M.filterWithKey (\k v -&gt; not $ null $ catMaybes [M.lookup x m | x &lt;- reverse (inits (init k))]) m


Now it's beneficial to inline list variables in both functions:

duplicates :: ResultTrie -&gt; String
duplicates m = mshow $ M.filter (\v -&gt; length v /= 1) m

prefixOverlap :: ResultTrie -&gt; String
prefixOverlap m = mshow $ M.filterWithKey (\k v -&gt; not $ null $ catMaybes [M.lookup x m | x &lt;- reverse (inits (init k))]) m


Now you can get rid of m in duplicates:

duplicates :: ResultTrie -&gt; String
duplicates = mshow . M.filter (\v -&gt; length v /= 1)


In prefixOverlap the line is too long, I can split it. I chose to extract the (\ -&gt; ) construct. Note that m is in more that 1 place so we cannot use ..

prefixOverlap :: ResultTrie -&gt; String
prefixOverlap m = mshow $ M.filterWithKey f m where
    f k _ = not $ null $ catMaybes [M.lookup x m | x &lt;- reverse (inits (init k))]


Now remove extra parentheses in the comprehension:

prefixOverlap :: ResultTrie -&gt; String
prefixOverlap m = mshow $ M.filterWithKey f m where
    f k _ = not $ null $ catMaybes [M.lookup x m | x &lt;- reverse $ inits $ init k]


From here I'll only work on f. 

Now a trickier part. Long chains of $ are equivalent to . following one $ in the end:

    f k _ = (not . null . catMaybes) [M.lookup x m | x &lt;- reverse $ inits $ init k]


not . null . catMaybes is a function of [Maybe a] -&gt; Bool that returns True only if there is any Just in the list. I can literally translate this sentence into Haskell: it's just any isJust:

    f k _ = any isJust [M.lookup x m | x &lt;- reverse $ inits $ init k]


Now even trickier: your comprehension is just a map:

    f k _ = any isJust $ map (\x -&gt; M.lookup x m) $ reverse $ inits $ init k


Congratulations, we can again turn a chain of $ into a chain of .:

    f k _ = any isJust . map (\x -&gt; M.lookup x m) . reverse . inits . init $ k


Now remember that . is associative so you can put parentheses in the middle of the chain wherever you want:

any isJust . (map (\x -&gt; M.lookup x m) . reverse) . inits . init


Now we have map something . reverse. And it doesn't matter when you reverse - before or after map:

map something f . reverse = reverse . map something


That is, map and reverse commute and we can swap them and remove parens:

any isJust . reverse . map (\x -&gt; M.lookup x m) . inits . init


Now we can put around any and reverse. It's clear that any works the same way for straight and reversed lists, that is, any something . reverse = any something, so we can throw reverse away:

any isJust . map (\x -&gt; M.lookup x m) . inits . init


And put back into prefixOverlap:

prefixOverlap :: ResultTrie -&gt; String
prefixOverlap m = mshow $ M.filterWithKey f m where
    f k _ = any isJust . map (\x -&gt; M.lookup x m) . inits . init $ k


Here is the full version of the code:

import Data.ListTrie.Map as M hiding (null, map)
import qualified Data.Map as Map
import Data.Maybe
import Data.List

type Event = Char
type Result = ()
type ResultTrie = M.TrieMap Map.Map Event [Result]

data XComposeString = XComposeString { keyEvents :: [Event], s :: Result }
  deriving (Eq, Show)

data XComposeFile = XComposeFile { strings :: [XComposeString] }
  deriving (Eq, Show)

constructTrie list = M.fromListWith (++) list

mshow :: ResultTrie -&gt; String
mshow list = M.showTrie list ""


duplicates :: ResultTrie -&gt; String
duplicates = mshow . M.filter (\v -&gt; length v /= 1)

prefixOverlap :: ResultTrie -&gt; String
prefixOverlap m = mshow $ M.filterWithKey f m where
    f k _ = any isJust . map (\x -&gt; M.lookup x m) . inits . init $ k


Note that I used fake Event and Result types. I also checked that old and new prefixOverlap are the same using QuickCheck and smallcheck libraries:

prefixOverlapOld :: ResultTrie -&gt; String
prefixOverlapOld m = M.showTrie list ""
  where
    list = M.filterWithKey (\k v -&gt; not $ null $ catMaybes [M.lookup x m | x &lt;- reverse (inits (init k))]) m

prop_foo x = prefixOverlap m == prefixOverlapOld m where
    m = M.fromList $ map (first (++ "X")) x


first is from Control.Arrow.

The prefixOverlap probably can still be improved with using lookupPrefix and/or children functions from Data.ListTrie.Map.

Here is my take:

prefixOverlapNew :: ResultTrie -&gt; String
prefixOverlapNew = mshow . M.unions . map (\(p,m) -&gt; M.addPrefix [p] m) . Prelude.filter (\(p, m) -&gt; M.size m /= 1 || (null $ fst $ head $ M.toList m)) . Map.toList . M.children1


Note that it differs from both original prefixOverlapOld and refactored prefixOverlap:

QuickCheck found that they differ on a map with 2 keys "fo" and "f". The prefixOverlapNew  version correctly shows both "fo" and "f" as they overlap. The original version(s) show only value on "fo" key which is dubuios.

Here is an improved version using list comprehension to combine filter and map:

prefixOverlapNew :: ResultTrie -&gt; String
prefixOvelapNew mm = mshow $ 
    M.unions [M.addPrefix [p] m | (p, m) &lt;- Map.toList $ M.children1 mm,  M.size m /= 1 || null (fst $ head $ M.toList m)]


Moved f out:

prefixOverlapNew mm = mshow $ M.unions [M.addPrefix [p] m | (p, m) &lt;- Map.toList $ M.children1 mm, f m] where
        f m =  M.size m /= 1 || null (fst $ head $ M.toList m)


Finally, a version in list monad, just for fun:

prefixOverlapNew :: ResultTrie -&gt; String
prefixOverlapNew mm = mshow $ M.unions $ do
        (p, m) &lt;- Map.toList $ M.children1 mm
        guard $ M.size m /= 1 || null (fst $ head $ M.toList m)
        return $ M.addPrefix [p] m


guard is from Control.Monad.