5

I have a class called Cls. In one of the instances of Cls, in one of the methods, I want to do some code analysis from within template haskell. I have to run a reify to start this inspection. Full files are included below.

class Cls a where
    infixl 6 +.
    (+.) :: Num a => v a -> v a -> v a

newtype Evaluate a = Evaluate a

instance Cls Evaluate where
    (+.) a b = doSomething $(typesOf '(+.)) a b
typesOf :: Name -> Q Exp
typesOf name = do
        _ <- reify name
        -- do something
        [e|["Double", "Int"]|]

However, at $(typesOf '(+.)), I get the following error:

‘+.’ is not in the type environment at a reify
In the untyped splice: $(typesOf '(+.))

If I compare this to, for example, makeLenses in the Control.Lens library, they do a similar thing. There it works. Which leads me to believe that, somehow, the '(+.) resolves to the function in the current instance declaration instead of the '(+.) in the class declaration.

Is this correct? If so, how do I actually use the class method as an argument to template haskell in this instance (no pun intended)?


{-# LANGUAGE TemplateHaskell #-}

module Main where

import Th

class Cls a where
    infixl 6 +.
    (+.) :: Num a => v a -> v a -> v a

newtype Evaluate a = Evaluate a

instance Cls Evaluate where
    (+.) a b = doSomething $(typesOf '(+.)) a b

-- Below here is not important
doSomething :: [String] -> a -> a
doSomething types a _ = trace (intercalate ", " types) a

main :: IO ()
main = putStrLn "Hello, Haskell!"
{-# LANGUAGE TemplateHaskell #-}

module Th where

import Language.Haskell.TH

typesOf :: Name -> Q Exp
typesOf name = do
        _ <- reify name
        -- do something
        [e|["a", "b"]|]

1 Answer 1

7

The problem is that (.+) is in the same declaration group as your splice. Names from the same declaration group are not visible to reify:

Accordingly, the type environment seen by reify includes all the top-level declarations up to the end of the immediately preceding declaration group, but no more.

You can split up declaration groups in several ways. For example you could move the class Cls ... declaration to another module. But perhaps the simplest way is to insert a $(pure []) top level splice between the class declaration and your splice like so:

class Cls a where
    infixl 6 +.
    (+.) :: Num a => v a -> v a -> v a

$(pure [])

instance Cls Evaluate where
    (+.) a b = doSomething $(typesOf '(+.)) a b

As Carl suggests, there is a function newDeclarationGroup in the template-haskell library that gives this pattern a more understandable name. You can use it like this:

class Cls a where
    infixl 6 +.
    (+.) :: Num a => v a -> v a -> v a

$(newDeclarationGroup)

instance Cls Evaluate where
    (+.) a b = doSomething $(typesOf '(+.)) a b
Sign up to request clarification or add additional context in comments.

2 Comments

Fantastic, that is an even easier fix then I was expecting. I didn't know that declaration groups would be so large, I thought they would implicitly be split after each top-level declaration. Thanks :)
Note that the docs suggest using hackage-content.haskell.org/package/template-haskell-2.23.0.0/… for this purpose. It has no different functionality, but it's a standardized name to indicate the purpose of the splice.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.