Closed fonghou closed 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))))
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.
Further thought on the second point, is there a generic Higher Kinded Applicative ?
rank2classes :)
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!
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/).
Semigroup/Monoid instance methods get errors like
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.