This might be a good thread for examples of using Data.Generic.
I’ll begin with a question. I’m at a yak-shaving point where I want to define Eq on this sum type:
data Fill
= Solid Color
| LinearGradient Color Color Percent
| RadialGradient Color Color Point
| NoFill
That means I need Eq for the various component types. Percent is easy:
newtype Percent = Percent Number
derive instance genericPercent :: Generic Percent
instance eqPercent :: Eq Percent where
eq = gEq
But Color (from purescript-colors) presents a problem. It has Eq, but it seems unavailable. This:
derive instance genericFill :: Generic Fill
… produces this:
No type class instance was found for
Data.Generic.Generic Color
while applying a function toSpine
of type Generic t0 => t0 -> GenericSpine
to argument $5
while checking that expression toSpine $5
has type GenericSpine
in value declaration genericFill
And as you’ve run into, Color doesn’t have Data.Generic.Generic instance, and I don’t think this is fixable afaik as Data.Generic.Generic tries to traverse the whole structure at once. edit: I guess this is not supposed to be the case. See below.
It seems worthy to update the readme for the generics library to point to generics-rep and note it’ll be deprecated in 0.12. When I first started working with generics I assumed that it was the default.
Yeah, I haven’t noticed that difference between the two implementations of Generic before. It’s pretty interesting that the Data.Generic.Rep (Generic)doesn’t require that types inside the to-be-made generic type themselves have instances of Data.Generic.Rep (Generic). I wonder why that is!
import Data.Generic as G
import Data.Generic.Rep as Rep
data Foo = Foo
data Bar = Bar Foo
-- derive instance genericBar :: G.Generic Bar
-- Fails type-checking
derive instance genericRepBar :: Rep.Generic Bar _
-- Passes type-checking
Actually, that was one of the main motivations from moving away from Data.Generic. We don’t want the appearance of things like DateTime or Map, which don’t (and shouldn’t!) have Generic instances to prevent us from defining Generic on our models.
There are other advantages to the Generics.Rep approach. They’re explained really well here.
… I get the “T is [not] a type constructor defined in the same module” error.
Similarly, to work with a record, it seems I also have to wrap it:
newtype Point = Point { x :: Number, y :: Number}
derive instance genericPoint :: Generic Point _
instance eqPoint :: Eq Point where
eq = GEq.genericEq
I suspect that the answer to “how do I define Eq on an unwrapped record?” is: “You don’t want to do that” rather than “like this: …” Yes?
This will be part of the 0.12 changes. Record will have an Eq instance, but also for Generic.Rep, RowToList and Record instances will be the way to handle generic record things, so it should all “just work”.
That is the answer, but here is an explanation. There is no Eq (Record r) instance right now - there can’t be because it would need RowToList which isn’t in Prelude, and the orphan instance restriction forces it to live with either Eq or Record (and Record is defined in Prim). Finally, you can’t define Eq (Record some_row_you_defined) because of the restriction on rows in instance heads. Allowing rows in instance heads would complicate type inference and run counter to the point of row polymorphism (that labels in rows have no globally-defined meaning).
As Nathan pointed out, this will all be addressed in 0.12 by moving RowToList into Prelude and defining one global Eq (Record r) instance.