nm shows symbols defined in an executable. GHC includes library name in symbols, so we can use this to inspect libraries that make it into the compiled binary. GHC uses z-encoding so we pipe the output of nm into an ad-hoc invocation of sed, viz.

$ nm -D $(which pandoc) \ | sed 's/\([^z]\)zi/\1./g ;s/\([^z]\)zm/\1-/g; s/\([^z]\)zd/\1$/g; s/ZC/:/g; s/zz/z/g' ... 000000000d50d930 D zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamzeroDictionaryHash1closure 000000000d50d930 D zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamzeroDictionaryHashclosure 000000000d50f198 D zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamZZlibclosure 00000000097c3688 T zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamZZlibconinfo 000000000d50c060 D zlib-0.6.3.0-2d9559b6e6a6701fec620c96115752eff9b4a69c5037bf15e8d98c669f8aeecfCodec.Compression.ZZlib.StreamzlibFormatclosure

To focus on only the library name, we use a regular to expression to capture and then Jacinda's ~. to deduplicate.

$ nm -D $(which pandoc) \ | sed 's/\([^z]\)zi/\1./g ;s/\([^z]\)zm/\1-/g; s/\([^z]\)zd/\1$/g; s/ZC/:/g; s/zz/z/g' \ | ja '~..?{`2 ~ /^(T|t)$/}{`3 ~* 1 /([A-Za-z][A-Za-z0-9\-]*-\d+(\.\d+)*)-[0-9a-f]{4}/}' aeson-2.2.1.0 aeson-pretty-0.8.10 appar-0.1.8 array-0.5.6.0 asn1-encoding-0.9.6 ...

Thus we have a list of libraries that provide functions that end up in the final executable. This is different from library dependencies that get linked, and in fact we can compare with diff (don't forget the | sort):

diff \ <(readelf -p '.debug-ghc-link-info' $(which ja) | ja -R, '.?{|`0 ~* 1 /-lHS([A-Aa-z][A-Za-z0-9\-]*\d+(\.\d+)*)/}' | sort) \ <(nm $(which ja) | sed 's/\([^z]\)zi/\1./g ;s/\([^z]\)zm/\1-/g; s/\([^z]\)zd/\1$/g; s/ZC/:/g; s/zz/z/g' | ja '~..?{`2 ~ /^(T|t)$/}{`3 ~* 1 /([A-Za-z][A-Za-z0-9\-]*\-\d+(\.\d+)*)\-[0-9a-f]{4}/}' | sort)

<( ... ) is the syntax for process substitution provided by the shell.

This gives us:

< array-0.5.6.0 < base-4.19.1.0 < binary-0.8.9.1 8d4 < composition-prelude-3.0.0.2 14,19d9 < ghc-bignum-1.3 < ghc-boot-th-9.8.2 < ghc-prim-0.11.0 < jacinda-2.0.3.0 < microlens-0.4.13.1 < microlens-mtl-0.2.0.3 23d12 < pretty-1.1.3.6 30d18 < rts-1.0.2 32,33d19 < stm-2.5.2.1 < template-haskell-2.21.0.0 37d22 < transformers-compat-0.7.2

Notably, this shows that microlens is linked at compile-time but there are no functions from microlens in the final executable. This means that GHC's simplifier worked and optimized away all lenses.

Again, this is tortuous, but it shows some of the features of the Unix compiler toolchain and the shell's impressive capabilities when marshaling text input/output. What dignity it loses from being jury-rigged is made up by its flexibility—IDEs offer no such capability.