Question: Why is there no `Pretty` instance for `Doc ann`?
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.
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 = unAnnotateThis was surprising to me, since it wasn't clear why using
prettyshould 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.)