Nikita Volkov
Nikita Volkov

Reputation: 43340

How to check whether instance exists for a polymorphic type with Template Haskell?

Suppose, I want to check whether an instance of Show exists for type [a] (which it does).

If I do this:

let t = ListT `AppT` VarT (mkName "a")
$(stringE . show =<< isInstance ''Show [t])

I get the following error:

Not in scope: type variable `a'
In the argument of reifyInstances: GHC.Show.Show [a]
In the expression: $(stringE . show =<< isInstance ''Show [t])
In an equation for `it':
    it = $(stringE . show =<< isInstance ''Show [t])

If I then do this:

let t' = ForallT [PlainTV (mkName "a")] [ClassP ''Show [VarT (mkName "a")]] t
$(stringE . show =<< isInstance ''Show [t'])

I get

"False"

Upvotes: 2

Views: 590

Answers (3)

Rudy Matela
Rudy Matela

Reputation: 6480

Starting with GHC 7.10 and Template Haskell 2.10, the original code provided by Nikita Volkov actually works out-of-the-box:

> let t = ListT `AppT` VarT (mkName "a")
> $(stringE . show =<< isInstance ''Show [t])
"True"

The answer of Nikita Volkov still works on GHC 7.10 and TH 2.10. It is pretty elegant IMHO, though I prefer using TupleT 0 in place of ConT ''Int (just because it is a simpler type):

> let t = ListT `AppT` TupleT 0
> $(stringE . show =<< isInstance ''Show [t])
"True"

For GHC 7.10 and TH 2.10, you can use any of the two methods. For GHC ≤ 7.8 and TH ≤ 2.9, you should use the second method listed in this answer.

Further Reading

  • GHC Ticket 6114: ghc: panic! occurred with use of isInstance, newName and a type splice;
  • GHC Ticket 7066: isInstance does not work for compound types.

Upvotes: 2

Nikita Volkov
Nikita Volkov

Reputation: 43340

Why the code in question doesn't work is explained in answer of user2407038.

Concerning the solution, while the following doesn't completely solve the problem, it can be used as a workaround. The [a] can be checked by supplying some specific type instead of variable a. You must know some specific existing instances for the class you're checking. In case of Show we know that there exists an instance for Int - we can use that:

Prelude Language.Haskell.TH> let t = ListT `AppT` ConT ''Int
Prelude Language.Haskell.TH> $(stringE . show =<< isInstance ''Show [t])
"True"

Upvotes: 2

user2407038
user2407038

Reputation: 14623

What you would like to do is actually see the types here before they are splices in. If you are in ghci, you can type set -ddump-splices which will print each splice after it is compiled. Then, with your example:

>undefined :: $(return t)
<interactive>:27:16-23: Splicing type
    return t ======> [a]
<interactive>:27:16:
    Not in scope: type variable `a'
    In the result of the splice:
      $(return t)

>undefined :: $(return t')
<interactive>:28:16-24: Splicing type
    return t' ======> forall a b. (Show a, Show b) => [a]

(The second one also gives an error - but for a different reason. You can have a type variable in the context that is not in the actual type. This error is essentially unrelated to your question).

As you can see the type [a] is not the same as the type forall a . [a]. When you write in (normal) haskell:

func :: [a] 

You are actually writing:

func :: forall a . [a]

However, Template Haskell doesn't insert foralls for you automatically (this would be very undesired behaviour). And the type

func :: forall . [a] 

will give you an error (the a is not in scope error) because a has not been bound in any visible scope, as one would expect! And the type forall . [a] is equivalent to the type generated by TH when you wrote return $ ListTAppTVarT (mkName "a").

edit:

If you are willing to just replace type vars with concrete types, then you are essentially just searching the type for your class. I don't know if this is your desired behavior, I guess I am struggling to find a case where this is useful. But if you can easily check if a typeclass instance is present in the context of a type:

existentialTypeContainsClass :: Name -> Type -> Bool
existentialTypeContainsClass clss (ForallT _ cxt t) = or $ map (boundByPred clss) cxt

boundByPred :: Name -> Pred -> Bool
boundByPred _ (EqualP _ _)    = False
boundByPred c (ClassP clss _) = c == clss

t = ListT `AppT` VarT (mkName "a")
t' = ForallT [PlainTV (mkName "a")] [ClassP ''Show [VarT (mkName "a")]] t

runTest = existentialTypeContainsClass ''Show t'

Upvotes: 3

Related Questions