Deprecate `NFData Doc`. It is a footgun.
- Dominant language
- Haskell
- Stars
- 75
- Forks
- 32
- PR merge metrics
- No merged PRs in 30d
Description
The `instance NFData Doc` (added in #13) must be considered harmful, as the size of `Doc` normal form _baloons_ even for small documents.
I shot myself badly in the foot when I refactored a `DeepSeq.force` on a rendered `Doc` (so, a `String`) to run on the `Doc` itself. (The purpose of the deep normalization is to bring exceptions to the surface.) The memory consumption exploded to the point of even making my terminal crash (on a 48GB machine!). The story can he read here and I link to the fatal loc:
- https://github.com/agda/agda/pull/8040
- https://github.com/agda/agda/pull/8040/commits/9ea5a10452725672f5d4862abe1ad32f5bc3806e#r2243728414
What is going on? The problem is the `Union` constructor in `Doc` that holds two documents that render to the same text (with maybe different layout). Relying on laziness, usually the second alternative is only used when the first does not work out well. However, `NFData`'s `rnf` will normalize both alternatives.
Also, the way that the `pretty` DSL is designed, common `Doc` constructors are implemented shallowly ("smart constructors"), so they distribute over `Union`. For instance, take `beside` which is ubiquitous because it is invoked whenever you concatenate documents horizontally (such as in `<>`, `<+>`, `hcat`, `hsep`...).
https://github.com/haskell/pretty/blob/8242bafa5d1be423f1404e73bc2e83e3022e95f1/src/Text/PrettyPrint/Annotated/HughesPJ.hs#L696 You can see that the arguments `g` and `q` are duplicated when `beside` distributes itself over a `Union`. And unions are also ubiquitous as they are introduced by `sep`s: https://github.com/haskell/pretty/blob/8242bafa5d1be423f1404e73bc2e83e3022e95f1/src/Text/PrettyPrint/Annotated/HughesPJ.hs#L722-L724 https://github.com/haskell/pretty/blob/8242bafa5d1be423f1404e73bc2e83e3022e95f1/src/Text/PrettyPrint/Annotated/HughesPJ.hs#L763-L766 So the normal forms of `Doc` are quadratic and _laziness is essential_ to make the `pretty` DSL work.
__Adding a normalizer like `NFData` is contrary to the whole design of this library.__
The addition of `NFData` was innocuous:
- https://github.com/haskell/pretty/pull/13
- https://github.com/haskell/pretty/issues/12
Contributor: https://github.com/haskell/pretty/issues/12#issue-36135975
> Would it be feasible to add an NFData instance for the Doc type, etc.? This would probably bee needed for https://github.com/haskell/pretty/issues/2.
Maintainer: https://github.com/haskell/pretty/issues/12#issuecomment-46738641
> Yes, this would be fine.
Don't get me wrong; both the Contributor and the Maintainer could have been me. One usually is just smarter in hindsight, when the calamity has struck.
This is the usual plight of software engineering: the original authors of the software leave, taking their deep understanding and unspoken *do*s and *don't*s with them, and the second generation has take over without having the expertise.
What is the way forward here?
**I think the `NFData Doc` instance should be deprecated,** i.e., equipped with a warning pointing out its hazards (e.g. pointing to this issue).
The original motivation for the introduction of `NFData Doc` also seems questionable in the light of this issue:
> This would probably bee needed for https://github.com/haskell/pretty/issues/2.
Since `NFData` degrades performance, it is probably not a tool to measure performance.
Related:
- https://github.com/haskell/pretty/issues/32
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.