Search code examples
haskelldslgadt

Transform a GADT without constraints to another GADT with constraints when such constraints hold


Can we transform a GADT without a given constraint on its constructors to a GADT that does have the said constraint? I want to do this because I want to get a deep-embedding of Arrows and do some interesting things with the representation that (for now) seem to require Typeable. (One reason)

data DSL a b where
  Id   :: DSL a a
  Comp :: DSL b c -> DSL a b -> DSL a c
  -- Other constructors for Arrow(Loop,Apply,etc)

data DSL2 a b where
  Id2   :: (Typeable a, Typeable b)             => DSL2 a a
  Comp2 :: (Typeable a, Typeable b, Typeable c) => DSL2 b c -> DSL2 a b -> DSL2 a c
  -- ...

We could try the following from function but that breaks quickly as we don't have the Typeable information for the recursive point

from :: (Typeable a, Typeable b) =>  DSL a b -> DSL2 a b
from (Id)       = Id2
from (Comp g f) = Comp2 (from g) (from f)

So instead we try to capture the transformation in a type class. However, that will break as well as we will be missing the Typeable b information as b is an existential.

class From a b where
  from :: a -> b

instance (Typeable a, Typeable b) => From (DSL a b) (DSL2 a b) where
  from (Id)       = Id2
  from (Comp g f) = Comp2 (from g) (from f)

Any other suggestions? Ultimately I want to create a deep-embedding of Category and Arrow together with Typeable information on the type parameters. This is so I can use the arrow-syntax to construct a value in my DSL and have fairly standard Haskell code. Maybe I have to resort to Template Haskell?


Solution

  • The problem with the recursive case is fatal to transforming DSL a b into DSL2 a b.

    To do this transformation would require finding the Typeable dictionary for the existential type b in the Comp case - but what b actually is has already been forgotten.

    For example consider the following program:

    data X = X Int
    -- No Typeable instance for X
    
    dsl1 :: DSL X Char
    dsl1 = -- DSL needs to have some way to make non-identity terms,
           -- use whatever mechanism it offers for this.
    
    dsl2 :: DSL Int X
    dsl2 = -- DSL needs to have some way to make non-identity terms,
           -- use whatever mechanism it offers for this.
    
    v :: DSL Int Char
    v = Comp dsl1 dsl2
    
    v2 :: DSL2 Int Char
    v2 = -- made by converting v from DSL to DSL2, note that Int and Char are Typeable
    
    typeOfIntermediate :: DSL a c -> TypeRep
    typeOfIntermediate int =
        case int of
            Comp (bc :: DSL2 b c) (ab :: DSL2 a b) ->
                typeOf (undefined :: b)
    
    typeOfX = typeOfIntermediate v2
    

    In other words if there was a way to do the conversion in general, you could somehow invent a Typeable instance for a type that doesn't actually have one.