Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation improvements #104

Open
OsePedro opened this issue Aug 6, 2024 · 4 comments
Open

Documentation improvements #104

OsePedro opened this issue Aug 6, 2024 · 4 comments

Comments

@OsePedro
Copy link

OsePedro commented Aug 6, 2024

The "Generic NFData deriving" documentation section shows how to derive instances of both NFData and NFData1 for Foo, but it doesn't say anything about when generic instances need NFData1 (the NFData1 documentation doesn't help either). As this is in the NFData class documentation, for people like me who first encountered NFData through a package that depends on it (Criterion in my case), this gives the impression that there must be a mysterious technicality that requires you to derive NFData1 for types with a single type parameter whenever you need an NFData instance. This is what I thought until I experimented and found that deepseq works fine on your Foo example without the NFData1 instance. E.g. in GHCi 8.10.7:

λ: data Foo a = Foo a String deriving(Generic,NFData)
λ: (Foo undefined "Hello" :: Foo Int) `deepseq` 1
*** Exception: Prelude.undefined

I see from the source code that rnf = rnf1 for functors like list, Maybe and Either, so I now wonder if NFData1 and NFData2 are just helper classes that are only useful when you're manually deriving NFData instances. Are there cases where NFData cannot be generically derived for unary/binary polymorphic types without NFData1/2? If not, can the derivation of NFData1 be removed from the Foo examples, and can a note be added stating that generic instances do not require NFData1/2? Or if NFData1/2 does need to be generically derived sometimes, can someone add an explanation to the documentation?

@mixphix
Copy link
Collaborator

mixphix commented Aug 6, 2024

The unary/binary polymorphism is not the same kind as you are thinking. NFData1 works on objects of kind k -> Type, not on terms of the kind Type. In your example, even though for any NFData a you have NFData (Foo a), you do not have NFData1 Foo. This is captured by the quantified constraints in the class declarations for NFData1 and NFData2, which instances then can assume without knowing beforehand on which type the application will be made.

I believe QuantifiedConstraints is only available on newer versions of GHC. Please update if you can, using ghcup or manually.

@OsePedro
Copy link
Author

OsePedro commented Aug 6, 2024

Yes, I'm aware that NFData1 expresses a constraint on types of kind k -> Type. But it's not my example -- it's the example in this part of the module documentation. I just removed NFData1 from the original example to demonstrate that you can derive an NFData instance without it.

From my point of view, the most important questions about NFData1 that the documentation doesn't make clear are:

  1. Am I right in thinking that the only people who need to call liftRnf or rnf1 are people who are manually deriving NFData instances?
  2. If not, what else is NFData1 useful for?

@mixphix
Copy link
Collaborator

mixphix commented Aug 7, 2024

NFData1 is to NFData what Eq1 is to Eq: a lawful typeclass. Where Eq1 "enforces" liftEq (==) == (==), we should always have liftRnf rnf == rnf for a NFData1 type constructor. In fact, the default implementation proves that law for any Generic1 data structure from its canonical representation Rep1.

The only part of the interface that need concern the day-to-day developer are the "Helper functions". These are normally what you want for making sure your own data is in RNF at certain evaluation steps. For example, f $!! x makes sure x is in RNF before applying f, but force (f $!! x) makes sure that the result is also in RNF before continuing to be used by the program.

@OsePedro
Copy link
Author

OsePedro commented Aug 8, 2024

Right, and isn't the "Generic NFData deriving" section also aimed at the day-to-day developer, whose goal is to apply the "Helper functions" to their own data types? If so, wouldn't it be less confusing to remove the derived Generic1 and NFData1 instances from the example Foo a type? After all, none of the "Helper functions" require NFData1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants