Embedding data with Template Haskell

When it is inappropriate to include data as a part of source code into a Haskell program (e.g. data is binary, data is too large, etc.), Template Haskell can be used to safely embed them. The file-embed package is a simple and clean implementation of this idea.

The concept can be expanded into importing data and doing more processing at the compile-time before embedding it. The external data is accessible from a Q action with runIO and Data.ByteString.readFile. However:

$ stack build
...
$ touch data/my3dmodel.bin
$ stack build
$ # nothing happened

Since the external file being imported is not part of the Haskell source code, making changes to it won’t trigger a re-build. This must be a huge trouble, because each time developers making changes to binary assets will have to somehow manually force the tool chain to build again. Is file-embed susceptible to this?

$ touch data/my3dmodel.bin
$ stack build
mypackage-0.1.0.0: unregistering (local file changes: data/my3dmodel.bin)
mypackage-0.1.0.0: build
...

No. Surprisingly, a file embedded with file-embed seems to have been marked as relevant and will trigger a re-build when changed. This is because the source code of file-embed includes:

embedFile :: FilePath -> Q Exp
embedFile fp =
#if MIN_VERSION_template_haskell(2,7,0)
    qAddDependentFile fp >>
#endif
  (runIO $ B.readFile fp) >>= bsToExp

The qAddDependentFile from Language.Haskell.TH.Syntax of the template-haskell package has the type signature of

qAddDependentFile :: FilePath -> m ()

and does the job of “marking as relevant.” As the code excerpt from file-embed suggests, the function has been added in 2.7 of the template-haskell package.

Conclusion

Make sure you call qAddDependentFile when you runIO $ B.readFile path in a Q action.