RandomB
RandomB

Reputation: 3747

How to call constructor from Template Haskell

I have function (let's call it mkSome) which constructs some data type with Template Haskell. It has typical signature Name -> Q [Dec]. Somewhere in its body I'm extracting constructors of another type with pattern-matching:

case tyCons of
  DataD ctx nm tyVars mbKind cs derivs -> ...

Type of those constructors cs instantiates some class like this:

class MyClass a where
  specialValue :: a

So, I'm iterating over those cs but I want to skip one of them which is equal to specialValue. Something like this:

[c | c <- cs, c /= specialValue]

Example:

data OtherData = A | B | C
instance MyClass OtherData where
  specialValue = C
$(mkSome ''OtherData) -- mkSome must skip C-constructor!

How to do this in Template Haskell's (with Con type: c is it) ? Sure, I can't simply call constructor to compare created value with a specialValue because it's AST node, not real constructor

Upvotes: 1

Views: 330

Answers (1)

user2407038
user2407038

Reputation: 14588

It depends entirely on how you want to use this expression. You can write e.g.

mkCons :: Name -> Q Exp
mkCons ty = do
  TyConI (DataD ctx nm tyVars mbKind cs derivs) <- reify ty
  let cons = ListE $ map (\(NormalC c _) -> ConE c) cs
  [| [c | c <- $(pure cons), c /= specialValue] |]

which is a splice whose result is the constructors of ty except specialValue.

But if you want to manipulate the resulting list within the splice (e.g. generate some code for all constructors except specialValue) then the situation is much more complicated. You'll need to have a nested splice which manipulates the result of the above splice:

mkSome :: Name -> Q Exp
mkSome ty =
  [| do e1 <- mapM lift $(mkCons ty)
        let mkD (ConE n) = DataD [] (mkName $ "Foo" ++ nameBase n) [] Nothing [] [] -- example function
        pure $ map mkD e1
    |]

Note also the use of lift; the result of $(mkCons ty) has type [OtherData] (in this case) but lift gives you the TH AST corresponding to those constructors.

Also note that the functions above use the Eq, Lift and MyClass instances of the given type. Due to the stage restriction, you have to define these instances in a seperate module than the use of the splice. So the following won't work:

module A where

import TH (mkSome)

data OtherData = A | B | C deriving (Lift, Eq)
instance MyClass OtherData where
  specialValue = C

$( $(mkSome ''OtherData) )

You must use it like so:

-- A.hs
module A where

data OtherData = A | B | C deriving (Lift, Eq)
instance MyClass OtherData where
  specialValue = C

-- B.hs
module B where

import TH (mkSome)
import A

$( $(mkSome ''OtherData) )

The result:

    mkSome ''OtherData
  ======>
    do { e1_adJ0 <- mapM
                      lift [c_adJ2 | c_adJ2 <- [A, B, C], (c_adJ2 /= specialValue)];
         let mkD_adJ1 (ConE n_adJ3)
               = DataD
                   [] (mkName $ ("Foo" ++ (nameBase n_adJ3))) [] Nothing [] [];
         (pure $ (map mkD_adJ1 e1_adJ0)) }


    (do { e1_adJ0 <- mapM
                       lift [c_adJ2 | c_adJ2 <- [A, B, C], (c_adJ2 /= specialValue)];
          let mkD_adJ1 (ConE n_adJ3)
                = DataD
                    [] (mkName $ ("Foo" ++ (nameBase n_adJ3))) [] Nothing [] [];
          (pure $ (map mkD_adJ1 e1_adJ0)) })
  ======>
    data FooA
    data FooB

Upvotes: 2

Related Questions