Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: Template Haskell and the scope

This code is compiled fine:

data None = None { _f :: Int }
type Simpl = Env

type Env = Int

However, I got an error with this code:

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

data None = None { _f :: Int }

type Simpl = Env

makeLenses ''None

type Env = Int

Error:

Not in scope: type constructor or class `Env'

I just added a single line makeLenses ''None between type declarations.
This means TemplateHaskell code could change the scope of type constructor?

Does anyone know the detail about this issue(or how to avoid this problem)?

like image 615
IruT Avatar asked Jan 02 '14 04:01

IruT


1 Answers

If you reorder your code as follows, it works:

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

data None = None { _f :: Int }

type Simpl = Env

type Env = Int

makeLenses ''None

When you use Template Haskell splices to add new top-level declarations to your code, as makeLenses does, the order of declarations in your code suddenly matters!

The reason is that normally compiling a Haskell program involves first collecting all the top-level declarations and reordering them internally to put them in dependency order, and then compiling them one by one (or group by group for mutually recursive declarations).

With new declarations being introduced by running arbitrary code, because GHC doesn't know which declarations makeLenses might need to run, and also it doesn't know which new declarations it will produce. So it can't put the whole file in dependency order and just sort of gives up and expects the user to do it themselves, at least for deciding whether declarations should go before or after the splice.

The only online reference I can find that explains this is in the original Template Haskell paper, section 7.2, where it says that the algorithm is:

  • Group the declarations as follows:
[d1,...,da]
splice ea
[da+2,...,db]
splice eb
...
splice ez
[dz+2,...,dN]

where the only splice declarations are the ones indicated explicitly, so that each group [d1,...,da], etc, are all ordinary Haskell declarations.

  • Perform conventional dependency analysis, followed by type checking, on the first group. All its free variables should be in scope.

So the problem here is that the first group of declarations before the splice is being handled separately to the second group after the splice, and it can't see the definition of Env.

My general rule of thumb is to put splices like this at the bottom of the file if possible, but I don't think it's guaranteed that this will always work.

like image 143
GS - Apologise to Monica Avatar answered Oct 17 '22 17:10

GS - Apologise to Monica