7

I'm trying to shift execution of some code to compile time with GHC 8 and template-haskel-2.11. The code looks like this:

myThHelper :: FilePath -> Q Exp
myThHelper path =
  runIO (compileThatFile path) >>= liftData

This is a simplified version of that code but it hopefully conveys what I'm trying to do.

Note the liftData function, it's new in template-haskell-2.11 and it promises to “lift” into expression any instance of Data. Very cool and it compiles.

However, when I use it like so:

main :: IO ()
main = do
  let compiled = $(myThHelper "/path/to/my/file/foo.txt")
  …

I'm getting the following error message from the compiler:

• Can't find interface-file declaration for variable Data.Text.Internal.pack
    Probable cause: bug in .hi-boot file, or inconsistent .hi file
    Use -ddump-if-trace to get an idea of which file caused the error
• In the first argument of ‘PName’, namely
    ‘Data.Text.Internal.pack ((:) 'f' ((:) 'o' ((:) 'o' [])))’
  In the first argument of ‘Template’, namely
    ‘PName (Data.Text.Internal.pack ((:) 'f' ((:) 'o' ((:) 'o' []))))’
  In the expression:

etc. Any idea what's going on and how to fix it?


I confirmed via experiments that the problem only manifests itself when data type to lift has Text in it. I'm going to open an issue.


And here it is.

Mark Karpov
  • 7,499
  • 2
  • 27
  • 62

1 Answers1

11

This seems to be because dataToQa expects the function that toConstr shows (which is "pack" for Text) to be in the same module the data type is defined. So liftData is looking for pack in Data.Text.Internal but pack is actually in Data.Text.

An easy way to fix it is to just write your own lift function for Text:

{-# LANGUAGE TemplateHaskell #-}

import qualified Data.Text as T
import Language.Haskell.TH.Syntax

liftText :: T.Text -> Q Exp
liftText txt = AppE (VarE 'T.pack) <$> lift (T.unpack txt)

myThHelper :: FilePath -> Q Exp
myThHelper path =
  runIO (compileThatFile path) >>= liftText

If text is deep in the structure you want to use, you can use dataToExpQ which lets you override the lift function for type-specific cases:

import Data.Data

liftDataWithText :: Data a => a -> Q Exp
liftDataWithText = dataToExpQ (\a -> liftText <$> cast a)
cchalmers
  • 2,896
  • 1
  • 11
  • 11
  • I fear that "easy way" won't work for me, because my data has `Text` not on top-level, but deep inside. – Mark Karpov Jul 04 '16 at 12:28
  • 2
    @Mark In that case you can use [`dataToExQ`](http://hackage.haskell.org/package/template-haskell-2.11.0.0/docs/Language-Haskell-TH-Syntax.html#v:dataToExpQ). I've updated my answer. – cchalmers Jul 04 '16 at 13:47
  • Now that is interesting. I'll check it once I have time and accept your answer if it works. – Mark Karpov Jul 05 '16 at 08:20
  • why casting to Text would work for any Data? cast succeeds when conversion happens between newtype wrappers of the same type. Why would `data Foo ~ Data.Text` ? – Daniil Iaitskov Dec 16 '21 at 17:15
  • @DaniilIaitskov You're thinking of [`coerce`](https://hackage.haskell.org/package/base-4.16.0.0/docs/Data-Coerce.html#v:coerce)? [`cast`](https://hackage.haskell.org/package/base-4.16.0.0/docs/Data-Typeable.html#v:cast) will only succeed for things that are exactly `Text` (not newtype wrappers). – cchalmers Dec 17 '21 at 20:11