This is a bit approximate, but how about something along these lines:
type Doc ctx
= ...
| Wrapper WrapFns ctx Doc
type alias WrapFns ctx =
{
wrap: ctx -> String -> (String, ctx)
unwrap: ctx -> String
}
The idea is that you can provide a set of ‘wrapper’ functions that modify the string output.
In the case of the ANSI console the ‘wrap’ function would place the ANSI control sequences to change color before the string to output. The ‘unwrap’ function would use the context to restore the old console colours outside of the wrapped section.
In the case of HTML the ‘wrap’ function would wrap the output in markup to change its color/style. The ‘unwrap’ function would not need to do anything, as the wrap function could take care of placing the output inside markup, unlike the console which needs more logic to restore the previous context; HTML is already nested, the console isn’t.
Then you would implement sets of wrap functions for color/style for the console or for HTML or whatever you want to output to. They would be completely separate to the pretty printer as the WrapFns type completely describes the interface needed between pretty printing and style.
As I say, a bit approximate so I may not have quite got the types right. The wrap functions would also need to be passed down into the normalized representation too.