Search code examples
haskellpretty-print

prettyprinter: Linebreak on narrow margin, mempty otherwise


I'm using the prettyprinter library to prettyprint foo(bar, baz), and when the margin is too narrow, I'd like it to look like:

foo(
  bar,
  baz)

How do I do that?

My best attempts so far:

> import Prettyprinter
> import Prettyprinter.Util
> let commas = punctuate comma

-- 'sep' and 'nest' seem to go well together, but the result lacks the linebreak
> putDocW 10 ("foo" <> parens (nest 2 (sep (commas ["bar", "baz"]))))
foo(bar,
  baz)

-- Using 'line'' inside 'nest' almost does the trick
> putDocW 10 ("foo" <> parens (nest 2 (line' <> sep (commas ["bar", "baz"]))))
foo(
  bar,
  baz)

-- Except 'line'' doesn't seem "mempty" enough
> putDocW 20 ("foo" <> parens (nest 2 (line' <> sep (commas ["bar", "baz"]))))
foo(
  bar, baz)

I thought what I want is line' because of the mention of mempty:

line' is like line, but behaves like mempty if the line break is undone by group (instead of space).

But perhaps I misunderstand what "undone by group" does.

It seems that none of the other ones (line, softline and softline') give better results.


Solution

  • I’m not confident this is the “right” way, but here’s a way:

    hcat
      [ "prefix"
      , nest 2 $ cat
        $ "("
        : punctuate (flatAlt "," ", ")
          [ "first"
          , "second"
          , "third"
          ]
      , ")"
      ]
    

    That is, hang the concatenation of the opening parenthesis and punctuated elements after the prefix, separating them with or without trailing space depending on whether they’re folded, and tack on the closing parenthesis.

    Alternatively, you could make your own variant of encloseSep that adds separators at the end rather than the beginning, and includes the opening delimiter in the cat:

    encloseSepEnd
      :: Doc ann -> Doc ann -> Doc ann -> [Doc ann] -> Doc ann
    encloseSepEnd open close sep docs = case docs of
      [] -> open <> close
      [doc] -> open <> doc <> close
      _ -> cat (open : suffixReverse (close : repeat sep) docs)
    
    suffixReverse
      :: (Foldable t, Semigroup a)
      => [a] -> t a -> [a]
    suffixReverse separators
      = snd . foldr go (separators, [])
      where
        go doc (sep : seps, acc) = (seps, doc <> sep : acc)
    
    hcat
      [ "prefix"
      , nest 2 $ encloseSepEnd "(" ")" ", "
        ["first", "second", "third"]
      ]
    

    Aside, honestly I find that I invariably have to replace bracketing functions like parens with their separate parts in order to achieve the results I want, and group never behaves how I wish it did.