quchen / prettyprinter

A modern, extensible and well-documented prettyprinter.
BSD 2-Clause "Simplified" License
293 stars 34 forks source link

Question: Why is there no `Pretty` instance for `Doc ann`? #227

Closed skilly-lily closed 1 year ago

skilly-lily commented 2 years ago

Question

Why is there no Pretty instance for Doc ann which functions as the identity?

On the surface, and without any detailed knowledge of the prettyprinter internals, it seems like an identity instance is trivial to add:

instance Pretty (Doc ann) where
  pretty = id

Looking through the history, I noticed that there was a general instance before, but one that removed all annotations:

instance Pretty (Doc ann) where
  pretty = unAnnotate

This was surprising to me, since it wasn't clear why using pretty should remove pre-existing annotations.

Rationale

Currently, we have a prettyprinter-based logging system that has the following form (simplified):

log :: Doc ann -> m ()
log = doLogging

someFunctionWhichLogsSomething = do
  value <- computation
  if somethingAbout value
    then log $ pretty "some simple log message"
    else log $ someFunctionWhichReturnsADoc
  pure value

Note the call to pretty in the then portion of the if statement. Those are absolutely everywhere in the codebase.

What we'd like to do is this:

log :: Pretty a => a -> m ()
log = doLogging . pretty

someFunctionWhichLogsSomething = do
  value <- computation
  if somethingAbout value
    then log $ "some simple log message"
    else log $ someFunctionWhichReturnsADoc
    --         ^ This function requires a Pretty instance for Doc ann
  pure value

Without the pretty instance, we're forced to do this, which is harder to migrate to, and is less obvious to developers:

log :: Doc ann -> m ()
log = doLogging

log' :: Pretty a => a -> m ()
log' = log . pretty

someFunctionWhichLogsSomething = do
  value <- computation
  if somethingAbout value
    then log' $ "some simple log message"
    else log $ someFunctionWhichReturnsADoc
  pure value

Note the required use of both log and log'. This is particularly ugly.

sjakobi commented 2 years ago

On the surface, and without any detailed knowledge of the prettyprinter internals, it seems like an identity instance is trivial to add:

instance Pretty (Doc ann) where
  pretty = id

Unfortunately it's not quite this easy:

ghci> instance Pretty (Doc ann) where pretty = id

<interactive>:4:42: error:
    • Couldn't match type ‘ann1’ with ‘ann’
      Expected: Doc ann -> Doc ann1
        Actual: Doc ann -> Doc ann
      ‘ann1’ is a rigid type variable bound by
        the type signature for:
          pretty :: forall ann1. Doc ann -> Doc ann1
        at <interactive>:4:33-38
      ‘ann’ is a rigid type variable bound by
        the instance declaration
        at <interactive>:4:10-25
    • In the expression: id
      In an equation for ‘pretty’: pretty = id
      In the instance declaration for ‘Pretty (Doc ann)’
    • Relevant bindings include
        pretty :: Doc ann -> Doc ann1 (bound at <interactive>:4:33)

Is there a trick to make the annotation type of the input and output Docs match up?

Looking through the history, I noticed that there was a general instance before, but one that removed all annotations:

instance Pretty (Doc ann) where
  pretty = unAnnotate

This was surprising to me, since it wasn't clear why using pretty should remove pre-existing annotations.

The relevant commit seems to be https://github.com/quchen/prettyprinter/commit/b653d592a337fa083859f79b0d893174a2d805a5. This was long before my involvement in this project, but it seems that the removal was motivated by the (performance) pitfall documented in the instance haddocks:

since this un-annotates its argument, nesting it means multiple, potentially costly, traversals over the 'Doc'.

Maybe @quchen can provide more context.

To get back to the instance, I wonder how to best avoid the unAnnotate pass. Maybe we could consider having an instance for Docs that cannot contain annotations, e.g.

instance Pretty (Doc Void) where
  pretty = unsafeCoerce

(dhall uses a similar trick.)