8

Consider the following:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}

data T = T1 | T2

data D (t :: T) where
  D1 :: D T1
  D2 :: D T2
  D3 :: D d
  D4 :: D T1

x1 :: [D T1]
x1 = [D1, D3, D4]

x2 :: [D T2]
x2 = [D2, D3]

Basically x1 is the list of all valid constructors for D T1, and x2 is the list of all valid constructors for D T2.

However, I want both this lists to reflect any additional constructors added to D, I don't want to hard code these lists like they are currently.

Is there a way to define x1 and x2 such that they are automatically generated from D?

Clinton
  • 22,361
  • 15
  • 67
  • 163
  • What you want can be achieved with `TemplateHaskell` or _generic programming_. Though exact functions require some time to write up. So maybe one who answers this question will give you exact functions. – Shersh Apr 21 '17 at 11:27
  • You can't do such a thing, or your generated type would be infinite (and hence not a valid type). In particular, why would the lists `[D3] :: [D Any]`, `[D3] :: [D (Any Any)]`, not be generated? – user2407038 Apr 22 '17 at 00:08
  • .... Perhaps you want such an output from an input like `(D, [T1,T2])` (where you specify the types to check), or you want to assuming that there are actually closed kinds (they aren't!) - you are still in a bad position to achieve what you want, at least in this example. The reason is you must perform unification between `forall d . D d` and `D X` (where X is whatever type) - and only the GHC api (which isn't easy to use, to say the least) provides access to the GHC type unification algorithm. – user2407038 Apr 22 '17 at 00:09

1 Answers1

2

Disclaimer - my TemplateHaskell-fu is almost non-existent - but I've investigated a bit which should give you a starting point to work with:

For those who don't know Template Haskell is a kind of meta-programming (language) that allows to write programs that run at compile time - it is type checked so it is safe (for some definition of safe, I think you can write programs that take infinite time to compile).

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH

data T = T1 | T2

data D (t :: T) where
  D1 :: D T1
  D2 :: D T2
  D3 :: D d
  D4 :: D T1

You can start by loading the file into GHCi (don't forget to :set -XTemplateHaskell there)

> typeInfo = reify ''D
> $(stringE . show =<< typeInfo)

typeInfo is a Q Info that allows you to extract information from a type (escaped by '') - the $(..) works like print.

This gives you the template haskell Expression that constructs your (G)ADT:

TyConI (
    DataD [] TMP.D [KindedTV t_6989586621679027167 (ConT TMP.T)] Nothing 
        [GadtC [TMP.D1] [] (AppT (ConT TMP.D) (ConT TMP.T1))
        ,GadtC [TMP.D2] [] (AppT (ConT TMP.D) (ConT TMP.T2))
        ,ForallC [KindedTV d_6989586621679027168 (ConT TMP.T)] [] (GadtC [TMP.D3] [] (AppT (ConT TMP.D) (VarT d_6989586621679027168)))
        ,GadtC [TMP.D4] [] (AppT (ConT TMP.D) (ConT TMP.T1))] [])

I with a bit of pattern matching - you can find the constructors that have either no restriction (ForallC) or a certain type (TMP.T1/TMP.T2) and then write some expression - to create a new type from those.

Right now I don't have enough time to supply that - but I will update this answer tonight.

EDIT

I looked some more at constructing types, but I have to admit I am a bit stuck myself - I deconstructed the type info kind of successfully.

d = reify ''D

dataName :: Info -> Maybe [Name]
dataName (TyConI (DataD _ _ _ _ x _) )= Just [t | NormalC t _ <- x]
dataName _ = Nothing

gadtDataUnsafe :: Info -> Q Exp
gadtDataUnsafe (TyConI (DataD _ _ _ _ cons _)) = return $ head $ concat [t | GadtC t _ _ <- cons]

I think it is doable to filter the T1/T2/forall d from here, tedious but doable to construct the lists.

What I failed at, is constructing the type - if I load the file into ghci I can execute

> f = $(gadtDataUnsafe =<< d)
>:t f
f :: D 'T1

but if I call that within the file I get the following error

error:
    • GHC stage restriction:
        ‘gadtData’ is used in a top-level splice, quasi-quote, or annotation,
        and must be imported, not defined locally
    • In the untyped splice: $(gadtData =<< d)

I know that for example Edward Kmett makes some th-magic creating lenses for stuff and there it works inside the same file, but the splice is not assigned to a variable - so maybe you need to construct the names for your lists inside the Q Exp - I guess mkName would be something you need there.

This concludes everything I found out - I hope it helps, I at least learnt a few things - for a full answer maybe someone smarter/more experienced with template haskell can supply some of his/her knowledge in a second answer.

epsilonhalbe
  • 15,637
  • 5
  • 46
  • 74
  • Did you accidentally post your answer too soon? – Zeta Apr 21 '17 at 14:48
  • Yes - I posted it accidentally a bit too early – epsilonhalbe Apr 21 '17 at 14:50
  • Which GHC version do you use? I get a completely other output in 7.10. There, all non-restricted are `NormalC`, and the `GadtC` is `ForAllC ...`. – Zeta Apr 21 '17 at 14:54
  • My GHC is `8.0.2` and [template-haskell 2.11.1.0](https://hackage.haskell.org/package/template-haskell-2.11.1.0/docs/Language-Haskell-TH.html) – epsilonhalbe Apr 21 '17 at 14:56
  • I'll have a look at that as well tonight – epsilonhalbe Apr 21 '17 at 15:01
  • You are understandably 'stuck' - you must perform type unification to solve this problem, and TH does not provide any access to the type unification algorithm used by GHC. (Also, re: " if I call that within the file .." - yes, that's just how TH works! you need to split definitions and uses of meta-programs into different files) – user2407038 Apr 22 '17 at 00:12