Lysxia / generic-data

Generic data types in Haskell, utilities for GHC.Generics
https://hackage.haskell.org/package/generic-data
MIT License
44 stars 9 forks source link

How to derive instances for HKD style records #19

Closed fonghou closed 5 years ago

fonghou commented 5 years ago

Hello,

I'm trying to modify test/record.hs example to derive Semigroup/Monoid instances for HKD style records (https://reasonablypolymorphic.com/blog/higher-kinded-data/).

type family HKD f a where
  HKD Identity a = a
  HKD f a = f a

data MyRecord f = MyRecord
  { _field1 :: HKD f Int
  , _field2 :: HKD f Bool
  } deriving Generic

Semigroup/Monoid instance methods get errors like

  Occurs check: cannot construct the infinite type: f ~ Alt f
     arising from a use of coerce

I don't have enough knowledge to understand these errors. Is it possible to use generic-data library to derive those instances with such change? If so, how to do it.

Thank you very much for any advice.

Lysxia commented 5 years ago

Good question!

I think intuitively I would have gone for

instance (forall a. Semigroup (HKD f a)) => Semigroup (MyRec f) where
  (<>) = gmappend
-- doesn't work, because of some ambiguity reason, but I'm not sure why precisely

After messing around a bit I found this somewhat equivalent version:

instance (forall a. (Semigroup (WrapHKD f a))) =>
  Semigroup (MyRec' f) where
  (<>) = gcoerceBinop (gmappend @(MyRec' (WrapHKD f)))

-- I just have a few duplicates MyRec, MyRec', MyRec'' in one file for experimentation

--

-- WrapHKD f a   is isomorphic to   HKD f a
-- and Semigroup (HKD f a) => Semigroup (WrapHKD f a)
newtype WrapHKD f a = WrapHKD (HKD f a)

type family HKD f a where
  ...
  HKD (WrapHKD f) a = WrapHKD f a
  ...

instance Semigroup (HKD f a) => Semigroup (WrapHKD f a) where
  (<>) = coerce ((<>) @(HKD f a))

--

gcoerceBinop :: forall a b. (Coercible (Rep a) (Rep b), Generic a, Generic b) => (a -> a -> a) -> (b -> b -> b)
gcoerceBinop (<>) x y = gcoerce (gcoerce x <> gcoerce y)

gcoerce :: forall a b. (Coercible (Rep a) (Rep b), Generic a, Generic b) => a -> b
gcoerce = to . coerce' . from where
  coerce' :: Coercible f g => f () -> g ()
  coerce' = coerce

Note that this is only for instances of Semigroup which are parametric in the parameter a.

If you need something that specifically relies on the individual types of the field (e.g., if they're all numbers that you can Sum), then you can reuse the constraint from gmappend's signature, delaying constraint solving for individual fields to the use sites (where hopefully, f is concrete):

instance Semigroup (Rep (MyRec'' f) ()) => Semigroup (MyRec'' f) where
  (<>) = gmappend

You could also use the IdentityT transformer to convert your "new-style HKD" records to "old-style HKD" records, i.e., without the type family:

newtype IdentityT f a = IdentityT { runIdentityT :: f a }  -- exists in the transformers library

type HKD f a where
  ...
  HKD (IdentityT f) a = f a
  ...

instance (forall a. Semigroup (f a)) => Semigroup (MyRec (IdentityT f)) where
  (<>) = gmappend

-- or, to get the same Alternative-based instance as in the record.hs example in generic-data

instance Alternative f => Semigroup (MyRec (IdentityT f)) where
  (<>) = gcoerceBinop (gmappend @(MyRec (IdentityT (Alt f))))
Lysxia commented 5 years ago

Full gist: https://gist.github.com/Lysxia/2366242814ee5f74d75af4196496a879

fonghou commented 5 years ago

Thank you very much for the detailed and informative anwser!

I've been playing with some ideas here https://github.com/FongHou/generic-data/blob/master/test/HKD.hs

Here are a few things I found out:

First, @isovector's validate doesn't work with HKD (IdentityT f) a = f a case, but your version does.

Second, following the ideas from https://www.benjamin.pizza/posts/2017-12-15-functor-functors.html, I wonder if there are generic versions of generalize and ffmap, which I called gpure and gffmap here.

Last, these HKD records have some issues with generic-lens using overloaded labels. Not sure why.

Although these generic type machinery is way over my head, it's fun to play and learn from awesome library authors like you and others.

fonghou commented 5 years ago

Further thought on the second point, is there a generic Higher Kinded Applicative ?

Lysxia commented 5 years ago

rank2classes :)

Lysxia commented 5 years ago

This issue lead me to add gcoerce and gcoerceBinop in 0.7.0.0. I'm closing this but feel free to come here again and open more issues with questions!