Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template Haskell: GHC stage restriction and how to overcome

I have the following code in a module:

{-# LANGUAGE TemplateHaskell #-}

module Alpha where

import Language.Haskell.TH
import Data.List

data Alpha = Alpha { name :: String, value :: Int } deriving (Show)
findName n = find ((== n) . name)

findx obj = sequence [valD pat bod []]
    where
        nam = name obj
        pat = varP (mkName $ "find" ++ nam)
        bod = normalB [| findName nam |]

And then I have the following in the main file:

{-# LANGUAGE TemplateHaskell #-}

import Alpha

one   = Alpha "One" 1
two   = Alpha "Two" 2
three = Alpha "Three" 3
xs    = [one, two , three]

findOne = findName "One"
findTwo = findName "Two"

$(findx three) -- This Fails
$(findx (Alpha "Four" 4)) -- This Works

main = putStrLn "Done"

I'd like the $(findx three) to create findThree = findName "Three" for me. But instead, I get this error:

GHC stage restriction: `three'
  is used in a top-level splice or annotation,
  and must be imported, not defined locally
In the first argument of `findx', namely `three'
In the expression: findx three

How do I overcome this? I would rather not have to define one, two, etc. in a separate file.

Second question is why does $(findx (Alpha "Four" 4)) work without problems?

like image 277
me2 Avatar asked May 02 '13 21:05

me2


1 Answers

I'm not very across Template Haskell myself, but based on my limited understanding the problem is that three is in some sense "still being defined" when GHC is trying to compile $(findx three), while all the component pieces of $(findx (Alpha "Four" 4)) are already fully defined.

The fundamental issue is that all the definitions in the same module affect the meaning of each other. This is due to type inference as well as mutual recursion. The definition x = [] could mean lots of different things, depending on the context; it could be binding x to a list of Int, or a list of IO (), or anything else. GHC might have to process the whole module to figure out exactly what it does mean (or that it's actually an error).

The code that Template Haskell emits into the module that's being compiled has to be considered by that analysis. So that means the Template Haskell code has to run before GHC has figured out what the definitions in the module mean, and so logically you can't use any of them.

Things that have been imported from other modules OTOH have already been fully checked when GHC compiled that module. There is no more information that needs to be learned about them by compiling this module. So those can be accessed and used before the compilation of the code in this module.

Another way to think about it: maybe three isn't actually supposed to be of type Alpha. Maybe that was a typo and the constructor should have been Alphz. Normally GHC finds out about those sorts of errors by compiling all the other code in the module that uses three to see whether that introduces an inconsistency or not. But what if that code uses or is used by things that are only emitted by $(findx three)? We don't even know what code that's going to be until we run it, but we can't settle the question of whether three is properly typed until after we run it.

It would of course be possible to lift this restriction a bit in certain cases (I have no idea whether it would be easy or practical). Maybe we could make GHC consider something to be "defined early" if is imported or if it only uses other things that are "defined early" (and perhaps has an explicit type signature). Maybe it could try compiling the module without running the TH code and if it manages to fully typecheck three before it runs into any errors it could feed that into the TH code and then recompile everything. The downside (besides the work involved) would be making it much more complicated to state what the exact restrictions are on what you can pass to Template Haskell.

like image 184
Ben Avatar answered Nov 11 '22 17:11

Ben