Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Couldn't match kind `*' against `#'

What the heck is going on here:

"Couldn't match kind `*' against `#'"

I was trying the following in GHCi using TemplateHaskell (ghci -XTemplateHaskell)

$(reify ''Show >>= dataToExpQ (const Nothing))

I was hoping to get an Exp out of this (which does have an instance of Show). I am doing this to insert information about haskell types in an application such that it is available as actual data, not as a string.

My goal is the following:

info :: Info
info = $(reify ''Show >>= dataToExpQ (const Nothing))

I really don't understand that error message, what is '#' anyway? If there is #, is there also # -> # or * -> #? Is it something that relates to kinds like kinds relate to types (though I would not know what that could be)?


Okay, so I do understand now that GHC has a hierarchy of kinds and that `#' is a special kind of unboxed types. All well and good, but why does this error pop up? Maybe unboxed types do not play well with genercis?

I'm not fully sure that this makes sense to me yet, since I would consider unboxed types being an optimazition performed by the compiler. I also thought that if an instance of Data exists, it needs to be there for all types that could possible be included in the data structure.

Upon further investigation I believe that Names pose the problem, is there a way to circumvent them in dataToExpQ? How to use that argument anyway?

like image 549
scravy Avatar asked Oct 04 '22 21:10

scravy


1 Answers

You're right, it is the Names that cause the problem. More specifically, the problem is that the NameFlavour data type has unboxed integers in some of its fields.

There's a Haddock note on the Data NameFlavor instance that raises some red flags. And if you click through to the source, you'll see that the gfoldl definition essentially treats the unboxed integers like integers. (There's really not much else choice…) This ultimately causes the error you're seeing because dataToExpQ — having been tricked by the deceptive Data NameFlavour instance — builds an Exp term that applies NameU to an (Int :: *) when NameU actually expects an (unboxed) (Int# :: #).

So the problem is that the Data instance for NameFlavour disobeys the invariant assumed by dataToExpQ. But not to worry! This scenario falls squarely under the reason that dataToExpQ takes an argument: the argument lets us provide special treatment for troublesome types. Below, I do this in order to correctly reify the NameFlavour constructors that have unboxed integer fields.

There may be solutions out there for this, but I'm not aware of them, so I rolled up the following. It requires a separate module because of the TH staging restriction.

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MagicHash #-}

module Stage0 where

import Language.Haskell.TH
import Language.Haskell.TH.Syntax

import GHC.Types (Int(I#))
import GHC.Prim (Int#)

unboxed :: Int# -> Q Exp
unboxed i = litE $ intPrimL $ toInteger $ I# i -- TH does support unboxed literals

nameFlavorToQExp :: NameFlavour -> Maybe (Q Exp)
nameFlavorToQExp n = case n of
  NameU i -> Just [| NameU $(unboxed i) |]
  NameL i -> Just [| NameL $(unboxed i) |]
  _ -> Nothing

And then the following compiles for me.

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH
import Language.Haskell.TH.Quote

import Generics.SYB
import Stage0

info :: Info
info = $(reify ''Show >>= dataToExpQ (mkQ Nothing nameFlavorToQExp))

CAVEAT PROGRAMMER The unboxed integers we're bending over backwards for here correspond to "uniques" that GHC uses internally. They are not necessarily expected to be serialized. Depending on how you're using the resulting Info value, this may cause explosions.

Also note when reifying Show, you're also reifying every instance of Show that's in scope.

  • There's a lot of them — this generates a pretty big syntax term.

  • As the documentation says, these instances do not include the method definitions.

HTH.

like image 129
nifr Avatar answered Oct 09 '22 14:10

nifr