pretty-printing with fixed indentation increase per sub-structure

Dear Cafe, I was looking for a way to pretty-print Haskell literals (with lists, tuples, records with named and positional notation) like this example ( Leftist { tree = Branch { left = Branch { left = Leaf, key = 4, right = Leaf } , key = 3 , right = Leaf } , refs = listToFM [ ( Ref 13, [ 0 ] ), ( Ref 17, [ ] ) ] } , [ Any, Any ] ) for each sub-structure, the indentation level (for the following lines) should increase - by a _fixed_ amount. in the above example: line break after "tree = Branch". But (missing from this example), line break _before_ the list starts in "{ foo = [ 42 , ... ] ... }". I found this impossible to do with wl-pprint but perhaps I did not try hard enough. Instead, I "invented" combinators `nest` and `skip` and made this prototypical implementation https://gitlab.imn.htwk-leipzig.de/autotool/all0/-/blob/master/todoc/src/Tex... (it has some explanatory text at the top) see also https://gitlab.imn.htwk-leipzig.de/autotool/all0/-/issues/960 but certainly this cannot be a new idea. While I do like the semantics (in the context of my application), I don't like the performance of my implementation. What am I doing wrong? It's just updating indentation level and current position, this should not take any time at all? Of course, it would be best if I don't need the implementation at all - if the effect could be achieved via some combinators in established libraries (that have optimized implementation). Any pointers appreciated. Best regards - J.

Is it possible that your size pre-calculation is inadvertently inducing quadratic behavior? I remember a pretty-printer implementation that generated both a line-broken and single-line version as it went, in order to avoid quadratic behavior due to backtracking. (IIRC, it would discard one as it went, if it determined that the single-line version of a sub-structure wouldn’t fit.) I don’t remember the exact details though so I know that’s a bit vague. I know you are caching size in order to avoid calculating it multiple times, but I’m wondering if that is still inducing double-traversal (once for size, once to generate the final text), and if that double could turn into a square. Just a possibility. Jeff
On Apr 17, 2023, at 3:51 AM, Johannes Waldmann
wrote: Dear Cafe,
I was looking for a way to pretty-print Haskell literals (with lists, tuples, records with named and positional notation) like this example
( Leftist { tree = Branch { left = Branch { left = Leaf, key = 4, right = Leaf } , key = 3 , right = Leaf } , refs = listToFM [ ( Ref 13, [ 0 ] ), ( Ref 17, [ ] ) ] } , [ Any, Any ] )
for each sub-structure, the indentation level (for the following lines) should increase - by a _fixed_ amount. in the above example: line break after "tree = Branch". But (missing from this example), line break _before_ the list starts in "{ foo = [ 42 , ... ] ... }".
I found this impossible to do with wl-pprint but perhaps I did not try hard enough.
Instead, I "invented" combinators `nest` and `skip` and made this prototypical implementation https://gitlab.imn.htwk-leipzig.de/autotool/all0/-/blob/master/todoc/src/Tex... (it has some explanatory text at the top) see also https://gitlab.imn.htwk-leipzig.de/autotool/all0/-/issues/960
but certainly this cannot be a new idea.
While I do like the semantics (in the context of my application), I don't like the performance of my implementation. What am I doing wrong? It's just updating indentation level and current position, this should not take any time at all?
Of course, it would be best if I don't need the implementation at all - if the effect could be achieved via some combinators in established libraries (that have optimized implementation).
Any pointers appreciated.
Best regards - J. _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

Dear Jeff,
Is it possible that your size pre-calculation is inadvertently inducing quadratic behavior?
Could be, but I don't think so, this looks pretty much linear ghci> T.length $ L.toStrict $ renderT $ vcat $ replicate 100000 "." 199999 (0.94 secs, 468,241,960 bytes) ghci> T.length $ L.toStrict $ renderT $ vcat $ replicate 200000 "." 399999 (1.83 secs, 935,759,344 bytes) ghci> T.length $ L.toStrict $ renderT $ vcat $ replicate 300000 "." 599999 (2.80 secs, 1,403,276,848 bytes) But that last expression with wl-pprint-text gives ghci> T.length $ L.toStrict $ displayT $ renderPretty 0.5 80 $ vcat $ replicate 300000 "." 599999 (0.27 secs, 426,008,296 bytes) Oh, and now I realize - my measurement was all wrong since ghci interprets my code (bytecode) but has the library compiled. (the asymptotics is still OK). When I compile my code first, I do get ghci> T.length $ L.toStrict $ renderT $ vcat $ replicate 300000 "." 599999 (0.27 secs, 459,427,808 bytes) and that's on par. (as it should be - I am using wl-pprint-text:SimpleDoc in the end.) So, with that out of the way, can we go back to discussing semantics please :-) Have you seen 'nest' (increase indentation level) and 'skip' (to that level, possibly with line feed) before? Is this `hang` of the Wadler-Leijen printer, somehow? The basic idea (p. 2) https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf is the same "indentation is added only at an explicit newline, not at the beginning of a string." That paper was written before the "dutch layout style" (separators at start of lines) became popular? So it is missing the examples that I am after. The libray https://hackage.haskell.org/package/wl-pprint-text-1.2.0.2/docs/Text-PrettyP... scares me because it mentions "three new primitives" (in extension of the paper). But it is super fast! Best regards, J.

Sorry I'm late to the party. I wanted something like that a long time
ago, and I thought "this should be easy" so I wrote a "simple" library
for it:
https://github.com/elaforge/karya/blob/work/Util/Format.hs
https://github.com/elaforge/karya/blob/work/Util/Pretty.hs
The reason for the scare quotes is that it turned out to be much
harder and more finicky than I thought. This implementation still has
at least one formatting bug which I did track down at some point, but
I believe it was a missing logical distinction which would be hard to
fix. But in fact I've been using it for about 9 years and it's worked
well enough. I haven't done any performance analysis, but I've never
felt the need for it, and I've printed some pretty large things.
So, I don't know if it's hard because it's actually hard, or because I
didn't do a great design, or because I wasn't motivated to put further
work into it after it got to good enough.
On Mon, Apr 17, 2023 at 3:51 AM Johannes Waldmann
Dear Cafe,
I was looking for a way to pretty-print Haskell literals (with lists, tuples, records with named and positional notation) like this example
( Leftist { tree = Branch { left = Branch { left = Leaf, key = 4, right = Leaf } , key = 3 , right = Leaf } , refs = listToFM [ ( Ref 13, [ 0 ] ), ( Ref 17, [ ] ) ] } , [ Any, Any ] )
for each sub-structure, the indentation level (for the following lines) should increase - by a _fixed_ amount. in the above example: line break after "tree = Branch". But (missing from this example), line break _before_ the list starts in "{ foo = [ 42 , ... ] ... }".
I found this impossible to do with wl-pprint but perhaps I did not try hard enough.
Instead, I "invented" combinators `nest` and `skip` and made this prototypical implementation https://gitlab.imn.htwk-leipzig.de/autotool/all0/-/blob/master/todoc/src/Tex... (it has some explanatory text at the top) see also https://gitlab.imn.htwk-leipzig.de/autotool/all0/-/issues/960
but certainly this cannot be a new idea.
While I do like the semantics (in the context of my application), I don't like the performance of my implementation. What am I doing wrong? It's just updating indentation level and current position, this should not take any time at all?
Of course, it would be best if I don't need the implementation at all - if the effect could be achieved via some combinators in established libraries (that have optimized implementation).
Any pointers appreciated.
Best regards - J. _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

Hi, 1. Have you seen the "prettiest printer" article here? https://jyp.github.io/posts/towards-the-prettiest-printer.html It says:
Wadler’s design fares somewhat better. It does not suffer from the above problem… /by default/. That is, it lacks the capability to express that sub-documents should be vertically aligned — compositionally.
...
*
Objection 2: /Leijen’s extension of Wadler’s design solves the issue: it provides an |align| combinator./
A package based on (a later version of) the design in this article is available here: https://hackage.haskell.org/package/pretty-compact This claims to be more ideal ("Prettiest") than either the Hughes ("Pretty") or Wadler ("Prettier") printers. I think it uses dynamic programming to avoid being too slow. If I understand correctly, GHC internally uses a version of the Hughes pretty printer, not the Wadler-Leijen one. 2. Doesn't the wl-print package already have a `nest` combinator? https://hackage.haskell.org/package/wl-pprint-1.2.1/docs/Text-PrettyPrint-Le... It also has the `align` combinator. If I remember correctly, these are part of the Leijen extension to Wadler. Are these not enough to get the behavior that you want? 3. Have you seen hindent? It has a module called HIndent.Pretty that might be relevant to laying out Haskell source. Does that help? -BenRI On 4/17/23 6:50 AM, Johannes Waldmann wrote:
Dear Cafe,
I was looking for a way to pretty-print Haskell literals (with lists, tuples, records with named and positional notation) like this example
( Leftist { tree = Branch { left = Branch { left = Leaf, key = 4, right = Leaf } , key = 3 , right = Leaf } , refs = listToFM [ ( Ref 13, [ 0 ] ), ( Ref 17, [ ] ) ] } , [ Any, Any ] )
for each sub-structure, the indentation level (for the following lines) should increase - by a _fixed_ amount. in the above example: line break after "tree = Branch". But (missing from this example), line break _before_ the list starts in "{ foo = [ 42 , ... ] ... }".
I found this impossible to do with wl-pprint but perhaps I did not try hard enough.
Instead, I "invented" combinators `nest` and `skip` and made this prototypical implementation https://gitlab.imn.htwk-leipzig.de/autotool/all0/-/blob/master/todoc/src/Tex... (it has some explanatory text at the top) see also https://gitlab.imn.htwk-leipzig.de/autotool/all0/-/issues/960
but certainly this cannot be a new idea.
While I do like the semantics (in the context of my application), I don't like the performance of my implementation. What am I doing wrong? It's just updating indentation level and current position, this should not take any time at all?
Of course, it would be best if I don't need the implementation at all - if the effect could be achieved via some combinators in established libraries (that have optimized implementation).
Any pointers appreciated.
Best regards - J. _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.
participants (4)
-
Benjamin Redelings
-
Evan Laforge
-
Jeff Clites
-
Johannes Waldmann