Extracting an Id with GHC.Generics

2019-04-16 08:03发布

问题:

How to extract an identifier (in this case an integer) from a structure using GHC.Generics?

I have an Id type:

newtype Id = Id { _id :: Int }

and a lot of types that use that type:

data VarId = VarId Name Id SortId
data FuncId = FuncId Name Id [SortId] SortId
data SortId = Name Id
-- ... and 20 more of these things

On top of that there are other types that wrap the types above, for instance, identifiers:

data Identifier = IdVar VarId
                | IdFunc FuncId
                | IdSort SortId
                -- ... and 20 more of these things

Now I need to extract the _id field from any of these types containing an Id value. Currently we have tons of boilerplate for this, and I want to scrap it away using generics.

At first I thought about defining a class:

class Identifiable e where
    getId :: e -> Id

    default getId :: (Generic e, GIdentifiable (Rep e)) => e -> Id
    getId = gGetId . from

class GIdentifiable f where
    gGetId :: f e -> Id

in such a way that you'd have an Identifiable instance only if there is one such Id type inside (in case that there are multiple of those like in FuncId above we return the first Id found when traversing the structure from top to bottom). Now the problem comes when I try to define the GIdentifiable instances for the product and sum. I would like to express something like:

instance (GIdentifiable a) => GIdentifiable (a :*: b) where
    gGetId (a :*: _) = gGetId a

instance {-# OVERLAPS #-} (GIdentifiable b) => GIdentifiable (a :*: b) where
gGetId (_ :*: b) = gGetId b

Which won't work because I'm defining duplicate instances.

I could redefine Identifiable so that getId :: e -> Maybe Id but this will remove some of the type safety, and introduce unnecessary checks when I know that a type contains at least one Id.

Is there a way to express this sort of case analysis when working with the type system?