haskelltemplate-haskelltype-variables

Is there an easy way to quote a type with constrained parameters?


> {-# LANGUAGE TemplateHaskell #-}
> import Language.Haskell.TH
> import Control.Monad

Let's say I have a class like Default

> class Default a where
>  def :: a

There's a straightforward way to define instances for types that also have a Monoid instance, like

instance Monoid a => Default a where
  def = mempty

but because of the overlapping problem and to make this more controllable one might provide a TH macro instead.

(Yes, I know we could use -XDerivingVia, I'm not interested in such a solution here.)

> makeMonoidDefault :: Q Type -> DecsQ
> makeMonoidDefault instT = sequence
>   [ InstanceD Nothing [] <$> [t| Default $instT |] <*> [d|
>         $(varP $ mkName "def") = mempty |] ]

This can than be invoked easily with a quoted type, like

makeMonoidDefault [t| Maybe () |]

to allow

*Main> def :: Maybe ()
Nothing

But this does not allow something like the parameterised instance

instance Semigroup a => Default (Maybe a) where
  def = mempty

That could be done with another macro:

> makeMonoidDefault' :: Q (Cxt,Type) -> DecsQ
> makeMonoidDefault' cxtInstT = do
>  (cxt, instT) <- cxtInstT
>  sequence
>   [ InstanceD Nothing cxt <*> [t| Default $(pure instT) |] <*> [d|
>         $(varP $ mkName "def") = mempty |] ]

But this is now much more awkward to actually use:

makeMonoidDefault' $ do
   tParam <- pure . VarT <$> newName "a"
   sgcxt <- [t| Semigroup $tParam |]
   maybet <- [t| Maybe $tParam |]
   return ([sgcxt], maybet)

or

makeMonoidDefault' $ do
   tParam <- pure . VarT <$> newName "a"
   ((,) . (:[])) <$> [t| Semigroup $tParam |]
                 <*> [t| Maybe $tParam |]

Solution

  • A hacky solution I came up with is this:

    -- Quote.hs
    {-# LANGUAGE TemplateHaskell #-}
    module Quote where
    
    import Language.Haskell.TH
    
    class Default a where
      def :: a
    
    makeMonoidDefault' :: Q Type -> DecsQ
    makeMonoidDefault' q = do
      t <- q
      case t of
        ForallT _ cxt instT -> sequence
          [ InstanceD Nothing cxt <$> [t| Default $(pure instT) |] <*> [d|
                $(varP 'def) = mempty |] ]
        _ -> fail "<some nice error message>"
    

    Then you can use it like this:

    -- Main.hs
    {-# LANGUAGE TemplateHaskell, ExplicitForAll #-}
    import Quote
    
    makeMonoidDefault' [t|forall a. Semigroup a => Maybe a|]
    
    main = pure ()