3 ms·
OK very interesting! I skimmed over the issue and some of the examples. It does seem like there are at least two schools of thought on the pretty printers --
by chubot 3y ago
OK very interesting! I skimmed over the issue and some of the examples.
It does seem like there are at least two schools of thought on the pretty printers -- those influenced by the Wadler pretty printing language (functional style), and those more influenced by go fmt (which does no line wrapping) and clang-format (which does).
There's a very recent paper linked here which has a good survey of the functional style. Table 1 is a nice summary of the different algorithms and IRs, a bunch of them appearing only in the last 10 years.
https://lobste.rs/s/aevptj/why_is_prettier_rock_solid https://lobste.rs/s/aevptj/why_is_prettier_rock_solid
A Pretty Expressive Printer (with Appendices) - https://arxiv.org/pdf/2310.01530.pdf https://arxiv.org/pdf/2310.01530.pdf
I'm probably biased toward the "non-PPL" style of clang-format because I think it's the best formatter I've used. It's fast and does what I want. I would probably attribute that to all the elbow grease and testing put in over the years, and being written in C++, not necessarily the wrapping algorithm.
But now that I see the examples, my suspicion is that some of the newer IRs and algorithms do address some of the Dart problems (though I didn't try anything!). Dart seems to have long expressions very much in common with functional languages, as opposed to Go, which apparently doesn't need wrapping at all!
I found this post linked from the paper, and it has 3 examples of formatting s-expressions that seem pretty similar to your examples.
And they discuss what IR is needed to express that. They talk about greedy algorithms vs. "non-local" dependencies.
https://jyp.github.io/posts/towards-the-prettiest-printer.html https://jyp.github.io/posts/towards-the-prettiest-printer.ht...
It seems like the IR of chunks, rules, and spans has a lot of similarity to what they call "groups" and so forth. Several years ago, long before I looked at any of this, I actually implemented a data structure printer with the same 2 rules as on page 2 of this paper - https://lindig.github.io/papers/strictly-pretty-2000.pdf https://lindig.github.io/papers/strictly-pretty-2000.pdf
i.e. try to fit all the parts on a single line, and if that fails, put each part on its own line. So the basic intuition is natural and obvious.
---
But I always want to see how these algorithms are tested with real languages, and how they perform. I'm still not sure how easy it is to encode all the language specific rules in these "cost functions", but I may give it a stab (I have more than one formatter to write!).
The paper says it's tested on Racket S-expressions, and that there is a large amount of non-trivial convention around that syntax. But I think that's still a far cry from C++ or Python, in both the amount of syntax and how many users / how much code, etc.
I do wonder about the problem of computing layouts from scratch vs. fixing existing layouts. And the problem of formatter upgrades causing churn. My impression is that go fmt tends to fix things, erring on the side of leaving code alone, and people seem to like that? I guess the fear is with the "global optimization" algorithms, the formatter will make a bunch of choices and it's hard to figure out why.