dreixel / generic-deriving

BSD 3-Clause "New" or "Revised" License
44 stars 32 forks source link

Newtype(s) to support DerivingVia #58

Closed dbaynard closed 5 years ago

dbaynard commented 5 years ago

Following MuniHac I've been learning to use DerivingVia. As some of my newtypes just use the default implementations, it might make sense to export them from this library (at no maintainance cost for pre-8.6 GHC).

Edit: Actually, it makes sense to have a single newtype Default a = Default a for any implementation which just uses the defaults.

I've only just started with this, but I believe most, if not all, classes in this library would benefit from this. Happy to PR.


For example, for Eq:

newtype AsEq a = AsEq a

instance (Generic a, GEq' (G.Rep a)) => Eq (AsEq a) where
  AsEq x == AsEq y = x `geqdefault` y

instance (Generic a, GEq' (G.Rep a)) => GEq (AsEq a) where
  AsEq x `geq` AsEq y = x `geqdefault` y

And for Enum:

newtype AsEnum a = AsEnum a

instance (Generic a, GEq a, Enum' (G.Rep a)) => Enum (AsEnum a) where
  toEnum = AsEnum . toEnumDefault
  fromEnum (AsEnum x) = fromEnumDefault x

instance (Generic a, GEq a, Enum' (G.Rep a)) => GEnum (AsEnum a) where
  genum = AsEnum . G.to <$> enum'

Then I can simply add the following, to use the default implementations without needing to explicitly write out the where geq = geqdefault line.

deriving via (AsEq MyEnum) instance GEq MyEnum

deriving via (AsEnum MyEnum) instance Enum MyEnum

My example is in https://github.com/dbaynard/generic-programming-munihac-2018/tree/record — I'm investigating how much ancilliary information I can ergonomically fit into type definitions.

dbaynard commented 5 years ago

Actually, I could combine them all into a single, Default newtype.

RyanGlScott commented 5 years ago

Sounds like a good idea to me! I'd happily accept a pull request implementing this idea.

You might also consider having another newtype for GFunctor, GFoldable, GTraversable, and GCopoint, since those classes have different kinds than the others.

dbaynard commented 5 years ago

I've done the Default instances at https://github.com/dbaynard/generic-deriving/tree/feat/deriving-via.

Instances for the higher kinded classes are in progress.

dbaynard commented 5 years ago

Current state

  1. I've implemented Default or Default1 for all the classes. I had to export an internal type from the Uniplate module to do so.
  2. Despite the module compiling, not all instances can be generated — there are type role issues for Uniplate and some Semigroup/Monoid issues I haven't yet investigated. I think the fix for the roles issue involves QuantifiedConstraints (good job it is in 8.6 too).
  3. I could use genvalidity-hspec in the test suite to ensure derived types meet the rules — which may be of benefit anyway.
  4. This stackoverflow answer and this example manage to get rid of the various helper classes — it may be worth going in that direction for the derived instances (though I suppose there could be performance issues)
RyanGlScott commented 5 years ago

Sounds promising! Can you submit a pull request?

In response to your points:

  1. Exporting internal types is fine—we already export several of them already.
  2. I'm not sure exactly what you mean here, but perhaps this would become clearer if I could see the code in a PR.
  3. While I appreciate the offer, this library has a very wide support window (back to GHC 7.0), so I'd prefer not to incur additional dependencies if I can avoid it. (I'm already depending on hspec, so we can at least test some things.)
  4. Again, I'm not sure what you mean by "get rid of the various helper classes".
dbaynard commented 5 years ago
  1. I'll PR now.

  2. I meant, it implements, say, Functor, but using neither GFunctor nor GFunctor', for use with DerivingVia. These Default data types could actually then be implemented in a separate package, which would not depend on this package. If the performance is the same, this PR wouldn't be as desirable.

RyanGlScott commented 5 years ago

I meant, it implements, say, Functor, but using neither GFunctor nor GFunctor', for use with DerivingVia. These Default data types could actually then be implemented in a separate package, which would not depend on this package. If the performance is the same, this PR wouldn't be as desirable.

Ah, I see what you mean. Yes, personally speaking, I would prefer that we only handle the G classes. The way I see it, the classes in this library are mostly provided for pedagogical purposes so as to provide simple examples of datatype-generic programming. If one wanted to derive, say, Functor, I would encourage them to use the generic-data library instead, which doesn't have the same dependency footprint/backwards compatibility constraints as this library.