haskellgenericsghc

How to define Generic instance manually?


It is assumed that instances of GHC.Generic class for data types should be generated automatically by GHC via deriving mechanism, but it is not working for row-types and anonymous records with implicit parameters. So, I am trying to define Generic instance manually and started the experiment with replicating Generic instance for a regular data type using GHCi to get generic representation, as preparation step, but even here I face unexplainable error from the type checker.

> import GHC.Generics
> data Point = Point { x :: Int, y :: Int } deriving (Show, Eq, Generic)
> from (Point 1 2)
M1 {unM1 = M1 {unM1 = M1 {unM1 = K1 {unK1 = 1}} :*: M1 {unM1 = K1 {unK1 = 2}}}}
> :i Rep Point
type instance Rep Point
  = D1
      (MetaData
         "Point"
         "RowTypeDemo.ImplicitParamsAsAlternativeToRowType"
         "row-type-demo-0.0.1-inplace"
         False)
      (C1
         (MetaCons "Point" PrefixI True)
         (S1
            (MetaSel
               (Just "x") NoSourceUnpackedness NoSourceStrictness DecidedLazy)
            (Rec0 Int)
          :*: S1
                (MetaSel
                   (Just "y") NoSourceUnpackedness NoSourceStrictness DecidedLazy)
                (Rec0 Int)))

Generic instance for Point based on GHCi output above:

data Point = Point { x :: Int, y :: Int } deriving (Show, Eq)
instance Generic Point where
  type Rep Point =
    D1
      (MetaData
         "Point"
         "RowTypeDemo.ImplicitParamsAsAlternativeToRowType"
         "row-type-demo-0.0.1-inplace"
         False)
      (C1
         (MetaCons "Point" PrefixI True)
         (S1
            (MetaSel
               (Just "x") NoSourceUnpackedness NoSourceStrictness DecidedLazy)
            (Rec0 Int)
          :*: S1
                (MetaSel
                   (Just "y") NoSourceUnpackedness NoSourceStrictness DecidedLazy)
                (Rec0 Int)))
  from :: Point -> Rep Point x
  from r = M1 {unM1 = L1 (M1 {unM1 = M1 {unM1 = K1 {unK1 = x r}} :*: M1 {unM1 = K1 {unK1 = y r}}})}

Type checker fails with:

• Couldn't match type: M1
                         i0 c0 (M1 i1 c1 (K1 i2 Int) :*: M1 i3 c2 (K1 i4 Int))
                       :+: g0
                 with: M1
                         C
                         (MetaCons "Point" PrefixI True)
                         (S1
                            (MetaSel
                               (Just "x") NoSourceUnpackedness NoSourceStrictness DecidedLazy)
                            (Rec0 Int)
                          :*: S1
                                (MetaSel
                                   (Just "y")
                                   NoSourceUnpackedness
                                   NoSourceStrictness
                                   DecidedLazy)
                                (Rec0 Int))
  Expected: Rep Point x
    Actual: M1
              D
              (MetaData
                 "Point"
                 "RowTypeDemo.ImplicitParamsAsAlternativeToRowType"
                 "row-type-demo-0.0.1-inplace"
                 False)
              (M1 i0 c0 (M1 i1 c1 (K1 i2 Int) :*: M1 i3 c2 (K1 i4 Int)) :+: g0)
              x
• In the expression:
    M1
      {unM1 = L1
                (M1
                   {unM1 = M1 {unM1 = K1 {unK1 = x r}}
                             :*: M1 {unM1 = K1 {unK1 = y r}}})}
  In an equation for ‘from’:
      from r
        = M1
            {unM1 = L1
                      (M1
                         {unM1 = M1 {unM1 = K1 {unK1 = x r}}
                                   :*: M1 {unM1 = K1 {unK1 = y r}}})}
  In the instance declaration for ‘Generic Point’    | 87 |   from r = M1 {unM1 = L1 (M1 {unM1 = M1 {unM1 = K1 {unK1 = x r}} :*: M1 {unM1

= K1 {unK1 = y r}}})} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I don't see where the problem is, because type family application Rep Point x should be equal to its value.

GHC version is 9.8.1

List of enabled extensions:

  DataKinds
  LambdaCase
  ImplicitParams
  OverloadedLabels
  OverloadedStrings
  TupleSections
  TypeFamilies
  UnicodeSyntax
  GADTs
  PolyKinds
  RankNTypes
  TypeOperators
  TypeApplications

Update:

Kudos to @leftaroundabout. GHCi generates wrong representation.

Following implementations satisfy the type checker.

  from :: Point -> Rep Point x
  from r = M1 {unM1 = (M1 {unM1 = M1 {unM1 = K1 {unK1 = x r}} :*: M1 {unM1 = K1 {unK1 = y r}}})}

  to (M1 {unM1 = (M1 {unM1 = M1 {unM1 = K1 {unK1 = xx}} :*: M1 {unM1 = K1 {unK1 = yy}}})}) = Point xx yy

Update 2

It was my fault not GHC one. I messed somehow and used sum type representation.


Solution

  • You have a sporadic L1 in your definition. That's a constructor for :+:, which doesn't make sense for your type.

    This definition compiles:

    from r = M1 {unM1 = M1 {unM1 = M1 {unM1 = K1 {unK1 = x r}} :*: M1 {unM1 = K1 {unK1 = y r}}}}
    

    or, more readably,

    from r = M1 . M1 $ M1 (K1 $ x r) :*: M1 (K1 $ y r)
    

    I rather doubt writing such an instance is sensible, though. Generally the whole point of a generic instance is that it allows you to define class instances without needing to write special code dealing with the structure of your type. If you're going to write such code anyway, why not just instantiate the class of interest right away? If your type is unusual enough to not support a derived Generic instance, then this would likely be more appropriate.