Haskell Generics tutorial - #40
Conversation
e2e09e2 to
044bc11
Compare
044bc11 to
4814873
Compare
| @@ -0,0 +1,3 @@ | |||
| resolver: lts-7.16 | |||
There was a problem hiding this comment.
Though not that important, lts-7.17 is out
Nenlantea
left a comment
There was a problem hiding this comment.
Hey Mark, I've added some comments to the tutorial, please take a look.
| description: The tutorial will get you up to speed with GHC generics quickly. | ||
| --- | ||
|
|
||
| The tutorial will get you up to speed with GHC generics quickly. It should |
There was a problem hiding this comment.
I think "This tutorial" is clearer than "The tutorial"
| Let's start from the beginning. What is generics? **Generics**, like in | ||
| other languages, **is a way to use the same code to manipulate different | ||
| data types**. We should note that in this regard the technique is very close | ||
| to much more common notion of polymorphism, which in Haskell comes in two |
There was a problem hiding this comment.
consider changing "to much more common" for "to the much more common"
| flavors: | ||
|
|
||
| * **Parametric polymorphism**, when we have type variables in functions/data | ||
| types. This allows the same function, such as `const` work with any types |
There was a problem hiding this comment.
Consider changing "such as const work" to "such as const to work"
|
|
||
| * **Parametric polymorphism**, when we have type variables in functions/data | ||
| types. This allows the same function, such as `const` work with any types | ||
| of arguments, as long as the more general types from function's signature |
There was a problem hiding this comment.
Consider changing "from function's signature" to "from a function's signature"
| these properties. The code is then applicable to any data type that has | ||
| the properties. | ||
|
|
||
| How generics are different? Generics allow to describe transformation that |
There was a problem hiding this comment.
it should be "How are generics different?" instead of "how generics are different?" because it's a question.
|
|
||
| Representation also has associated metadata. It may look messy and difficult | ||
| to read, but I'll explain you the logic behind it in a moment, it should | ||
| clarify why it's done like this. |
| once. | ||
|
|
||
| The `c` type is auto-generated by the compiler and encodes metadata on type | ||
| level. Why not to store the metadata on value level, in `M1` constructor? |
There was a problem hiding this comment.
Remove "to" from "Why not to store the metadata on value level, in M1 constructor?" leaving "Why not store the metadata on value level, in M1 constructor?"
|
|
||
| ## Example: counting constructor fields | ||
|
|
||
| I think now we're dangerous enough to implement a simple (and pretty |
There was a problem hiding this comment.
Consider changing "dangerous" with "adventurous"
There was a problem hiding this comment.
Oh no, it should be dangerous :-D
| ``` | ||
|
|
||
| This way we can eat the cake and have it: deriving is easy and otherwise no | ||
| ugly details is visible! |
There was a problem hiding this comment.
Change "is" for "are" because you are referring to the plural "details".
| * [The `generics-sop` package on Hackage](https://hackage.haskell.org/package/generics-sop) | ||
| * [Typeable and Data in Haskell](http://chrisdone.com/posts/data-typeable) — | ||
| the blog by Chris Done contains some straightforward examples of generic | ||
| programming using `Typeable` and `Data` type class |
There was a problem hiding this comment.
Please add at the end a you might also like to read list of blogs or tutorials you thing the audience of this tutorial might like from our own site, we'd like to give people an outlook on other interesting things we've been working on.
There was a problem hiding this comment.
Not sure about this. The section should be relevant to with respect to the topic of the tutorial. If we want to add a section of links like "other our articles you might be interested in", it should be done on higher level, maybe in the template that is used to render tutorials, not in text of tutorial. This is just my opinion though.
|
@juanpaucar can you please do a technical review on this? |
|
@Nenlantea Thank you for your review, I've made the necessary corrections. |
| --- | ||
|
|
||
| This tutorial will get you up to speed with GHC generics quickly. It should | ||
| be noted that generics is not something academic and useless, quite the |
There was a problem hiding this comment.
Punctuation/grammar tweaks:
...and useless; quite the...
...(and errors associated with it)...
| overhead. In fact, by the time you get to the end of the tutorial, I hope | ||
| you'll agree that generics are easy to use and easy “to get” as well. | ||
|
|
||
| Let's start from the beginning. What is generics? **Generics**, like in |
There was a problem hiding this comment.
... what are generics?...
...languages, are a way...
There was a problem hiding this comment.
I'll agree on this, although I talked about generics as a language feature, but perhaps if the language feature's name is in plural form, this version is more correct.
| signature can be made equal to concrete types we want to work with by | ||
| instantiating/fixing the type variables. | ||
|
|
||
| * **Ad-hoc polymorphism**, which allows to perform a computation abstracted |
| ## The idea about generics | ||
|
|
||
| Haskell's mechanism that opens the possibility for such a thing as generics | ||
| is, of course, type classes and ad-hoc polymorphism. The ability to describe |
There was a problem hiding this comment.
I would remove the "of course"; we should assume that nothing is obvious to the reader.
| Haskell's mechanism that opens the possibility for such a thing as generics | ||
| is, of course, type classes and ad-hoc polymorphism. The ability to describe | ||
| a data type in terms of a set of combinators is our property, encapsulated | ||
| by the `Generic` type class. Data type that is an instance of that type |
There was a problem hiding this comment.
... A data type that is and instance of the Generic type class... (I would be redundant here for clarity)
... work with a representation of...
|
|
||
| The process can be described like this: | ||
|
|
||
| 1. Given data type gets `Generic` instance automatically, as it can be |
There was a problem hiding this comment.
... A given data type gets a Generic ...
... help of the DeriveGeneric...
| magically get all the useful methods of that type class. | ||
|
|
||
| If you know about `Data` and `Typeable` type classes, then you probably know | ||
| that it's possible to do something similar using information which methods |
There was a problem hiding this comment.
... using the information those type classes provide. Data and Typable are beyond the scope of this tutorial, but you can start with ...
There was a problem hiding this comment.
Data and Typeable are not libraries, they are standard type classes :-)
There was a problem hiding this comment.
This change doesn't say anything about Data and Typable being libraries, so I'm not sure what your comment is referring to.
There was a problem hiding this comment.
Sorry, but I'm pretty sure it did say somethnig of the sort before you edited it :-) It doesn't matter though.
| tutorial, we won't touch this topic. You can start with | ||
| this [blog post by Chris Done](http://chrisdone.com/posts/data-typeable) if | ||
| you're interested. | ||
|
|
|
|
||
| Step 2 is not so simple as it sounds though, at least not until we examine | ||
| the details of this business more closely. | ||
|
|
There was a problem hiding this comment.
... represent the shape of a data type...
| What could they look like? Well, if I showed you the data types as they are | ||
| right now, you would probably run away in fear, cursing the tutorial and | ||
| Haskell's generics. I have a better idea. Let's start with the simplest | ||
| thing possible and then iterate asking ourselves how to work around some |
| we arrive at the definitions that are actually used. | ||
|
|
||
| For better or worse, algebraic data types lock us into a view on the world | ||
| that consists of sums and products (another Haskell's feature that makes |
There was a problem hiding this comment.
...another Haskell feature that makes the existence of a generics framework possible; generics ...
Could we make GADTs a link to another tutorial or some documentation about GADTs?
|
|
||
| * Constructors without arguments. Let this be something like this: `data U1 | ||
| = U1`. | ||
|
|
There was a problem hiding this comment.
What are L1 and R1 and why are they unnecessary for the product types?
There was a problem hiding this comment.
I think it'll be obvious to anyone who is familiar with Haskell's data types. In short, sum data type means that you can have one of several options:
data Foo = Foo | Bar | BazThis type is inhibited by 3 values, represented by three data constructors.
To represent it generically we would need something like this:
Foo = L1 U1
Bar = R1 (L1 U1)
Baz = R1 (R1 U1)
You see, we need need this sum type to hold two data constructor “options” and then we can nest them and represent any sum type.
With product types, you multiple number of possible values that inhibit a type, i.e. with a tuple:
(Foo, Foo)Number of possible values is 3 × 3 = 9, because tuple includes one Foo value and another one. This is why this is called product types. This is why this is called algebraic data types. So with products you don't need two different constructors, because you have “everything at once”.
There was a problem hiding this comment.
I am quite familiar with algebraic data types in Haskell, but what you explained in the above comment was not obvious to me. Perhaps this should have been obvious to me, but I am the target audience for this blog post, so you should assume that this will not be obvious to the reader. The above explanation, perhaps a little more concisely, would be a great addition to the tutorial.
| * Products: `data (f :*: g) = f :*: g`. | ||
|
|
||
| Not bad for a start. Let's try use this representation to derive a `Functor` | ||
| instance. Deriving a such an instance means that we should provide `fmap` |
There was a problem hiding this comment.
... provide the function fmap, which looks ...
| ```haskell | ||
| fmap :: (a -> b) -> Rep f -> Rep f | ||
| ``` | ||
|
|
There was a problem hiding this comment.
(replace "where" with) ...Here, Rep f maps to the type of the generic ...
| fmap :: (a -> b) -> Rep f -> Rep f | ||
| ``` | ||
|
|
||
| …where `Rep f` maps to the type of generic representation of `f :: * -> *`. |
| extra `p` parameter as a dummy type index that has no meaning. | ||
|
|
||
| The authors of the generic extension went with the second option, and I | ||
| can't blame them. We'll see that there are a lot of wrappers already and we |
There was a problem hiding this comment.
I am a little confused by this sentence: We'll see that there ...
What are the "wrappers" in this case? Would we have more or fewer with option 1 above?
There was a problem hiding this comment.
I guess it should become clearer as you read though the tutorial (will be even clearer on second read). It also requires some understanding of Haskell to get it though.
There was a problem hiding this comment.
Yes, this is a bit clearer on a second look. The first option would define a whole other set of "wrappers" on top of the ones we have already defined.
|
|
||
| With `Rec0`, we can finally build representation of `Maybe a`: | ||
|
|
||
| ```haskell |
|
|
||
| Let's derive a different representation that works with `* -> *` kinds: | ||
|
|
||
| ```haskell |
| -- | | ||
| -- +--- L1 and R1 are from our representation of sum types | ||
| ``` | ||
|
|
There was a problem hiding this comment.
Could we add in a sentence or two explaining the motivation for coming up with a second representation of Maybe? Also, which representation is the one actually used by ghc? Or are both used depending on context?
There was a problem hiding this comment.
Both are useful (and used). GHC can derive the first representation when you derive Generic and the second one when you derive Generic1. This is explained later in the tutorial.
Yes, we could add something. I guess I did a bad job if it's not clear, this is one of main points of the tutorial. Or maybe the topic is just hairy for less-experienced Haskellers.
|
|
||
| Type classes that map types to their representations are called `Generic` | ||
| (for type classes that work with `*` kinds) and `Generic1` (for type classes | ||
| that work with `* -> *` kinds). They live in the module called |
There was a problem hiding this comment.
...GHC.Generics, together ...
... build the data type ...
| ``` | ||
|
|
||
| `from` and `from1` map values of data types to their generic | ||
| representations. `Rep` and `Rep1` are associated type functions (the feature |
There was a problem hiding this comment.
... that take the type of data ...
... and yield the type of its ...
| actual value of target data type. This is done via `to` and `to1`. The good | ||
| thing about this, of course, that GHC can derive `Generic` and `Generic1` | ||
| for us automatically with `DeriveGeneric` language extension enabled. | ||
|
|
There was a problem hiding this comment.
...Now let's open GHCi and try to infer Rep for some type: ...
Perhaps we should be even more explicit about how to start GHCi to get this output. We could tell them to clone the repo, run stack init, then stack ghci or stack repl.
| Ah OK, there is just a little bit more to this representation thing… | ||
|
|
||
| ## Metadata wrappers | ||
|
|
| ```haskell | ||
| newtype M1 i c f p = M1 { unM1 :: f p } -- ‘f p’ is what lives inside, U1 for example | ||
| -- ^ ^ | ||
| -- | | |
| The `c` type is auto-generated by the compiler and encodes metadata on type | ||
| level. Why not store the metadata on value level, in `M1` constructor? Well, | ||
| if it were there, we would have to provide it if we wanted to generate some | ||
| values, and providing metadata for already existing data type is certainly |
| level. Why not store the metadata on value level, in `M1` constructor? Well, | ||
| if it were there, we would have to provide it if we wanted to generate some | ||
| values, and providing metadata for already existing data type is certainly | ||
| something that only compiler can do properly. With the current approach, |
There was a problem hiding this comment.
... approach, a given data type determines the type of its representation, including its metatdata, ...
| * Every constructor is wrapped in `C1`, which provides information about | ||
| constructor (such as constructor name, fixity, and whether it's a record). | ||
|
|
||
| * Every argument of constructor is wrapped with `S1` (even if it's not |
|
|
||
| Look | ||
| [at the Haddock](https://hackage.haskell.org/package/base-4.8.2.0/docs/GHC-Generics.html#t:Datatype), | ||
| to find out names of functions that help extract the metadata. Using them is |
There was a problem hiding this comment.
So is the "wrapped data" a type representation?
There was a problem hiding this comment.
Wrappers are part of the representation. As we have seen wrapping in M1 or L1 adds context to whatever is inside of it.
| instance (CountFields (a p), CountFields (b p)) => CountFields ((a :*: b) p) where | ||
| countFields (a :*: b) = countFields a + countFields b | ||
| ``` | ||
|
|
| defaultCountFields = countFields . from | ||
| ``` | ||
|
|
||
| But here is the catch, the code above does not compile. The `CountFields` |
There was a problem hiding this comment.
Question 1: What is this kind Constraint?
Question 2: How can a type class have a kind? I thought only types had kinds.
There was a problem hiding this comment.
Constraints have the kind Constraint :-) For example show :: Show a => a -> String, where the Show a thing is not something magical, you can manipulate it too, for example you can get it from a type function. So it needs a kind.
Type class does not have a kind per se, but every type class has a corresponding constraint (like Show type class has Show :: * -> Constraint, because we need a way to express the idea of constraint and it should be well-kinded, so here for example it says give me a type and I'll give you a constraint that will require that type to have an instance of Show type class).
There was a problem hiding this comment.
That's great, and makes things much clearer for me, thanks! I think this explanation would also enrich the blog post if added in.
There was a problem hiding this comment.
Also in this case, we should remove "The" and begin the sentence with CountFields.
There was a problem hiding this comment.
We could also be more explicit, and rather than saying "something", we could say:
... but we are giving it (Rep a), which has the kind ...
There was a problem hiding this comment.
Or even:
... giving it (Rep a) :: * -> *
| But here is the catch, the code above does not compile. The `CountFields` | ||
| has the kind `CountFields :: * -> Constraint`, but we give it something of | ||
| kind `* -> *`. It's not going to take our abuse like this! | ||
|
|
There was a problem hiding this comment.
Do we need the CountFields1 "helper class" in addition to our original CountFields1 class, or does it replace it?
There was a problem hiding this comment.
We need both. It's typical (when you work with generics) for type classes that work with * things to need an auxiliary type class like this in addition to the main type class.
| Having dealt with generic implementation of the functionality of interest, | ||
| let's put it all together and use a special GHC extension to allow the user | ||
| derive type classes without knowing anything about generics. | ||
|
|
| let's put it all together and use a special GHC extension to allow the user | ||
| derive type classes without knowing anything about generics. | ||
|
|
||
| For generic implementation to work without user's definition we need to |
| derive type classes without knowing anything about generics. | ||
|
|
||
| For generic implementation to work without user's definition we need to | ||
| provide it as the default definition. As you have already seen, generic |
There was a problem hiding this comment.
... involves a Generic constraint, and ...
| language extension, allows to give a different type signature for default | ||
| implementation of a type class: | ||
|
|
||
| ```haskell |
There was a problem hiding this comment.
This is a good catch.
| provide it as the default definition. As you have already seen, generic | ||
| implementation often involves `Generic` constraints and it would be ugly to | ||
| add them to every type class (on the left hand side of `=>`) just to make | ||
| deriving easier. The `default` keyword, enabled by the `DefaultSignartures` |
| default countFields :: (Generic a, CountFields1 (Rep a)) => a -> Natural | ||
| countFields = defaultCountFields | ||
| ``` | ||
|
|
There was a problem hiding this comment.
... can have our cake and eat it too: ...
| ugly details are visible! | ||
|
|
||
| ## Conclusion | ||
|
|
There was a problem hiding this comment.
Consider rephrasing as:
Generics are a powerful means of automating the process of defining type class instances, which is error-prone and boring if done by hand.
|
|
||
| ## Conclusion | ||
|
|
||
| Generics is a powerful means to automate error-prone and boring deriving. |
There was a problem hiding this comment.
... However, this feature is helpful beyond ...
| ## Conclusion | ||
|
|
||
| Generics is a powerful means to automate error-prone and boring deriving. | ||
| The feature is however helpful beyond just that use-case, as with a bit of |
|
|
||
| Generics is a powerful means to automate error-prone and boring deriving. | ||
| The feature is however helpful beyond just that use-case, as with a bit of | ||
| creativity it allows to reason about data types generically (for example, we |
| in a type safe way (useful, for example, in combination with Quick Check's | ||
| value generation). Finally, there are quite a few very interesting packages | ||
| that complement or build on top of GHC generics. Once you feel comfortable | ||
| with vanilla generics, |
| * [Documentation for `GHC.Generics` on Hackage](https://hackage.haskell.org/package/base-4.8.2.0/docs/GHC-Generics.html) | ||
| * [The page on Haskell Wiki](https://wiki.haskell.org/GHC.Generics) | ||
| * [The `generics-sop` package on Hackage](https://hackage.haskell.org/package/generics-sop) | ||
| * [Typeable and Data in Haskell](http://chrisdone.com/posts/data-typeable) — |
There was a problem hiding this comment.
... This blog post by ...
... using the Typable and Data type classes.
| @@ -0,0 +1,21 @@ | |||
| MIT License | |||
There was a problem hiding this comment.
Mark,
Thanks for writing this post! It is really well written and structured. I learned a lot editing it. I have tried to understand Generics before and found myself with a headache trying to take in too much at once. Saving the MetaData info for the end was a wise choice.
Most of my comments are small grammar fixes about missing articles, subjects an objects, e.g. a, the, we and us. My biggest question is about this tricky p parameter. It seems like that is a hard thing to explain, but I hope my questions in the comments below give you some inspiration for how to explain it just a little more clearly.
Cheers!
Nathan
975e730 to
3dacde5
Compare
Much appreciated!
3dacde5 to
52cfa21
Compare
Ready for review.