haskell / pretty

Haskell Pretty-printer library
Other
71 stars 31 forks source link

Adding emptyLine document #26

Open osa1 opened 9 years ago

osa1 commented 9 years ago

Empty lines are useful to separate blocks in printed programs, but unless I'm getting the API wrong, currently only way to produce a empty newline is to use text "". empty doesn't work, because it's unit of $$ and $+$.

The problem with text "" is that when combined with nest, it produces trailing white space. For example, this:

nest 4 (text "" $+$ text "block")

Generates this:

    $
    block$

I propose implementing an emptyLine document, like this:

emptyLine :: Doc a
emptyLine = NilAbove Empty

This works fine, for example, this program:

print $ nest 4 $ emptyLine $$ text "blah"
print $ nest 4 $ text "blah" $+$ emptyLine

prints this:

$
    blah$
    blah$
$
$

Which is not bad, but now it's printing an extra new line at the end.

So I think there are two problems:

  1. This use of NilAbove is actually invalidating some internal invariants. Even though I couldn't manage to produce a broken example with this new doc, in the code it's mentioned that:

    1) The argument of NilAbove is never Empty. Therefore
      a NilAbove occupies at least two lines.

    Which this change clearly invalidates.

  2. We need to make some changes in some of the combinators to handle extra newline printed in the case of text "blah" $+$ emptyLine. I think it's printed because of this invalidated invariant.

So at this point I'm hoping to get some comments and ideas. Do you think this is a correct way of doing this? Is there any other way to produce new lines without producing trailing white spaces?

Also, some help with changing internals would be great.

Thanks.

UPDATE: I just tried this program:

print $ nest 4 $ emptyLine $$ (nest 4 $ text "blah")
print $ nest 4 $ (nest 4 $ text "blah") $+$ emptyLine

And it produced this:

$
        blah$
        blah$
$
    $

It's good to see that next line after the blah is not indented. Last indented empty line should be fixed when we remove that line.

UPDATE 2: Here's a broken example:

print $ nest 4 $ emptyLine <> (nest 4 $ text "blah")
print $ nest 4 $ (nest 4 $ text "blah") <> emptyLine

Output:

$
    blah$
        blah$
            $

An emptyLine should never be indented, that's the whole point of it. Documentation sometimes mention "height" and "width", maybe we should be able to say "emptyLine has height 1 and width 0".

dterei commented 9 years ago

I'm not sure what the best approach to this is right now sorry. On the surface it seems that it may be challenging to get what you want as it sort of breaks the modularity here of things like nest. I'm not going to have any time to look at this for a few weeks sorry, but please ping me again if I forget.

andreasabel commented 5 years ago

Me too.

I am hitting the same problem, trailing spaces produced by nest when document contains empty lines. I second the original proposal, to add the concept of an empty line to the internal document structure and to expose it as emptyLine with one law being nest n emptyLine == emptyLine.

The current situation is bad since it forces the programmer into non-compositionality when using the pretty printing framework. By this I mean that Docs cannot be combined compositionally, but it is often needed to keep a document containing empty lines as [Doc] where the empty lines go between the documents in such a list. On such a list of documents, nest can be safely applied (mapped), and only in the very end this list may be converted into a single document, inserting the empty lines between. This is an ugly workaround for the missing functionality of this library, which is the de facto standard pretty printer still.