I haven't looked very closely, but I'm suspicious of this code from "instance Block Piece"
ListLike l -> forM l (\obj -> ...)
>>= (return . mconcat)
The "forM" means that "l" will be traversed once and create an output list, which will then be mconcat'd together. The list has to be created because of the monadic structure imposed by forM, but if the result of the mconcat isn't demanded right away it will be retained as a thunk that references the newly-created list.
I'd suggest that you replace it with something like
ListLike l -> foldM (\(!acc) obj -> ... >>= return . mappend acc) mempty l
Here I've justed added a bang pattern to the accumulator. If whatever is being returned has some lazy fields, you may want to change that to use deepseq instead of a bang pattern.
Also, "foo >>= return . bar" is often regarded as a bit of a code smell, it can be replaced with "bar <$> foo" or "bar `liftM` foo", or sometimes something even simpler depending on circumstances (but IMHO sometimes it's more clear to just leave it alone).
The heap profile does look like a space leak. The line
<StrappedTemplates-0.1.1.0:Text.Strapped.Render.sat_sc1z>
is a thunk (you can tell because it's in '<>' brackets), so whatever is referencing that is not strict enough. Sometimes another heap profile report, e.g. "-hc" or maybe "-hy" will give more useful information that lets you identify what exactly "sat_sc1z" is. You could also try compiling with -ddump-stg, which will dump the intermediate STG output which usually shows those names. But then you'll probably also need to re-run the profile, since the names change between compilations. Also IIRC some of values aren't named until the cmm phase, but that's harder to map back to Haskell so if you can identify the code from stg it's simpler.
John L.