1

I'm passing a record with the following structure to a Template Haskell function:

module Editor.App where

data WithMaybe
data WithoutMaybe

type family TypeSelector a b where
  TypeSelector WithMaybe b = Maybe b
  TypeSelector WithoutMaybe b = b

data MyRecord a = MyRecord
  { field1 :: TypeSelector a Int
  , field2 :: TypeSelector a String
  }

$(myTHFunction ''MyRecord)

Inside myTHFunction I'm calling reify and it is correctly giving me the following type-Info:

TyConI 
  (DataD 
    []
    Editor.App.MyRecord 
    [KindedTV a_6989586621679348600 StarT] 
    Nothing 
    [RecC Editor.App.MyRecord 
      [ ( Editor.App.field1
        , Bang NoSourceUnpackedness NoSourceStrictness
        , AppT  (AppT (ConT Editor.App.TypeSelector) (VarT a_6989586621679348600)) 
                (ConT GHC.Types.Int) )
      , ( Editor.App.field2
        , Bang NoSourceUnpackedness NoSourceStrictness
        , AppT  (AppT (ConT Editor.App.TypeSelector) (VarT a_6989586621679348600)) 
                (ConT GHC.Base.String) )
      ]
    ] 
    []
  )

However, in my application logic, I cannot proceed with an unapplied KindedTV a_6989586621679348600 StarT. So, my question is:

  • how do I "apply" this type-variable in TH
  • or, how do I apply it before passing it to TH. I tried $(myTHFunction ''(MyRecord SomeSelector)) but that doesn't work.
Saurabh Nanda
  • 6,373
  • 5
  • 31
  • 60
  • 1
    If you are hoping that the fields will have reduced types (ie. the type family will be "evaluated" in the info returned) you're completely out of luck. TH doesn't let you tap into that part of the compiler. – Alec Oct 23 '18 at 00:56
  • @Alec do you know **any** way that this can be achieved? It is not necessary that TH has to do this "type-family evaluation". I am fine if the type-family expression is evaluated _outside_ of TH, and the resultant type is passed to TH. – Saurabh Nanda Oct 23 '18 at 02:59
  • The only avenue I can think of that would let you access the sort of machinery you need would be a GHC plugin. Even then, I'm not sure how you'd feed the result back out to TH. :/ – Alec Oct 23 '18 at 04:29

1 Answers1

1

This seems like possibly two questions:

  1. You want to know how to substitute a free type variable in a type.

    The best way to do this I know of is to use applySubstitution from the th-abstraction package, which also helpfully includes normalized representations of datatypes across GHC versions so you don’t tether yourself to a single version of Template Haskell.

  2. You want to know how to pass an applied type using TH instead of a single name.

    The quoted syntax—i.e. 'ValueName or ''TypeName—quote names, which correspond simply to references to bindings. MyRecord SomeSelector clearly isn’t a name, it’s a type. Therefore, you should use the type quotation syntax instead of the name quotation syntax, which is written [t|MyRecord SomeSelector|]. This quotation will give you a value of type Language.Haskell.TH.Syntax.Type, which is a structured datatype that represents the AST of an arbitrary Haskell type.

As far as I know, there is no function written down in some library that allows you to go directly from a Type with the shape T X ... to the reified list of constructors associated with T, with its argument variables instantiated with types X .... This is, of course, a partial function: [t|forall a. a|] quotes a valid Type, but it is not of the shape T X ..., so you’ll have to handle that failure mode yourself (and hopefully report a useful error message). It can be defined in terms of applySubstitution without too much effort, but you will have to do a bit of groveling through the Type you receive to extract the right information.

Alexis King
  • 43,109
  • 15
  • 131
  • 205
  • Thanks! I think the `[t|MyRecord SomeSelector|]` solution might solve my problem. Do you know where the `t` function / QQ is defined? I want to see it's type signature and internal workings. I tried it on stackage and GHCi, but nothing worked, eg `:t TH.t`, `:i TH.t`, `:kind! TH.t` – Saurabh Nanda Oct 23 '18 at 01:38
  • Also, does `applySubstitution` "evaluate" the "type-family expression", or does it does a mechanical / blind substitution of the type-variable? I'm trying to reconcile solution 1 with Alec's comment on the question. – Saurabh Nanda Oct 23 '18 at 01:42
  • I got `[t|MyRecord WithMaybe|]` working, but the `Type` that it is returning is - `AppT (ConT Editor.App.MyRecord) (ConT Editor.App.WithMaybe)` - which means that the type-family expression is not being evaluated. Will `applySubstitution` do things differently? – Saurabh Nanda Oct 23 '18 at 01:47
  • Okay, tried `applySubstitution` as well. Unfortunately, it is not really "applying / expanding / evaluating" the type-family expression. It is simply substituting the free type-variable with `WithMaybe / WithoutMaybe` and leaving that "unapplied". – Saurabh Nanda Oct 23 '18 at 02:58
  • GHC has language extension TypeApplications now. – Johnny Liao Feb 26 '22 at 03:41