How does Template Haskell play with qualified names?

Hi everyone, I have never written a Template Haskell function, but for the first time I am using one from a library, and I am encountering the following situation:

The library I’m using is apecs-stm. As it reexports symbols from the apecs library, the library documentation explicitly recommends that I import it qualified, which is what I do (under the alias AS).

Now apecs-stm features a Template Haskell function makeWorld, which generates code that includes the Has symbol (from apecs-stm too). The problem is, when I try to compile my code, the compiler complains because Has is not in scope.

Indeed, Has is not in scope, only AS.Has is, since I imported apecs-stm qualified.

I think that I could easily bypass the issue by importing Has unqualified on top of the qualified import, but I feel this might not be a good practice, and I worry I might be misunderstanding how TH works/is supposed to be used. In particular, I am surprised that using this TH function requires me to import some symbols one way (unqualified) instead of another.

So I guess my question is:
What is the right way to deal with TH code that generates unqualified library symbols?

2 Likes

As a workaround you can import Apecs.Core unqualified. That brings Has into scope.

But really, I think the apecs library can and should fix this issue by referring to things with name quotes instead of mkName "Has" as they do in their source here.

3 Likes

As a workaround you can import Apecs.Core unqualified. That brings Has into scope.

I was considering something like that indeed.

I think the apecs library can and should fix this issue by referring to things with name quotes

Ohh so basically replacing mkName "Has" with ''Has would fix the problem? If so, I will probably file an issue.

2 Likes

I think so, but I’m not completely certain. At least it’s worth a try.

1 Like

Ok, thank you for the help! It’s not trivial for me to determine whether this would be a fix*, so if anyone knows about it, feel free to tell me, so I know whether it’s worth suggesting it.

* I don’t know how to interpret the documentation on this; testing TH’s behavior would require me to setup a TH project, which is a bit of work; or modify the library locally, which I don’t know how to do.

It is as I expected. Here’s a demonstration:

module A where

x :: Int
x = 5
{-# LANGUAGE TemplateHaskellQuotes #-}
module B where

import A
import Language.Haskell.TH

y :: Q Exp
y = varE 'x

y' :: Q Exp
y' = varE (mkName "x")
{-# LANGUAGE TemplateHaskell #-}
module C where

import B

x :: Int
x = 0

z :: Int
z = $y

z' :: Int
z' = $y'

You can test this with:

$ cabal repl -b template-haskell
...
ghci> :l C.hs
...
[1 of 3] Compiling A                ( A.hs, interpreted )
[2 of 3] Compiling B                ( B.hs, interpreted )
[3 of 3] Compiling C                ( C.hs, interpreted )
Ok, three modules loaded.
ghci> z
5
ghci> z'
0

Commenting out that definition of x in C.hs results in this error:

C.hs:13:7: error: Variable not in scope: x :: Int
   |
13 | z' = $y'
   |       ^^
Failed, two modules loaded.
1 Like

Thank you for testing. I will try compiling my code with a modified version of the library too (I was just taught how to do that). I filed an issue on the library issue tracker already.

2 Likes

Nice going. Sounds like a great opportunity for a first contribution to something template-haskell related :slight_smile:

2 Likes