Pretty Printing

The way pretty printing is handled in Poly/ML changed in version 5.3. This describes the functions and datatypes that were added in that version. The old mechanism, using PolyML.install_pp, is deprecated and may be removed in a future release.

The basic model, described in the paper by D.C. Oppen ACM ToPLAS Vol. 2 No. 4 Oct 1980, is unchanged but instead of a set of imperative functions pretty printing is structured around the PolyML.pretty datatype and the pretty printing algorithm is now functional rather than imperative.

datatype pretty =
     PrettyBlock of int * bool * context list * pretty list
   | PrettyBreak of int * int
   | PrettyString of string

and context =
    ContextLocation of location
  | ContextParentStructure of string * context list
  | ContextProperty of string * string

withtype location =
   { file: string, startLine: int, startPosition: int, endLine: int, endPosition: int }

PrettyString s contains a string to be printed. PrettyBlock(indent, consistent, context, items) defines a block of items which may be strings, breaks or blocks. The items will be retained on a single line if possible. The indent parameter is an indentation that will be added to the current indentation if the block has to be broken. Note that this does not apply to the first item in the block which will always be printed with the current indentation. The consistent parameter indicates whether the block is to be broken consistently (true) or not (false). If it is true then if the block will not all fit on a line and must be broken then it will be broken at all the breaks in the block whether this is necessary or not. If it is false it will only be broken where necessary. Neither of these parameters are used if the block will fit on a line. PrettyBreak(blanks, breakOffset) indicates a break between items. If the line is not broken at this point then blanks is the number of space characters that will be inserted. If the line is broken at that point then instead the following item is indented by an extra breakOffset spaces.

The context and location types are primarily used by the IDE when providing error messages. For most purposes the context argument to PrettyBlock can be the empty list. ContextProperty can be used by a user-supplied pretty printer to provide extra information which may be useful if the result of pretty printing is to be processed by a user function. It is not produced by Poly/ML pretty printers and the default printers ignore this item. ContextParentStructure was used to deal with types inside structures in an early draft and will probably be removed.

A pretty printer can be associated with a datatype using PolyML.addPrettyPrinter.

val addPrettyPrinter: (int -> 'a -> 'b -> pretty)-> unit

This function has a polymorphic type but is specially handled by the compiler. addPrettyPrinter pp installs a pretty printer pp where pp has arguments depth printArgTypes value. The first argument, depth, is the print depth. This is a value that indicates how much of the data structure should be displayed. If this value is zero or negative the pretty printer should always print a simple string such as PrettyString "...". The third argument, value, is a value of the datatype. When installing a pretty printer there must be sufficient type constraint so that the compiler is able to determine the type unambiguiously. The second argument, printArgTypes, is only used for polytypes i.e. datatypes defined as 'a t or ('a, 'b', 'c ...) t. It is not used for monotypes. If the type takes a single argument then printArgTypes has type 'a * int -> pretty and is the function that will generate the pretty data structure for the argument type. The int argument is the adjusted print depth and should normally be one less than the value of depth. If the type takes multiple arguments then printArgTypes is a tuple with each field being a function of type 'a * int -> pretty that is used for the corresponding argument of the datatype.

As well as PolyML.addPrettyPrinter there some other functions in the PolyML structure that may be useful.

val prettyRepresentation : 'a * int -> pretty

This function returns the pretty structure that can be used to print a value of the given type up to the specified depth. It is similar to PolyML.print in being infinitely overloaded. It can be useful when writing a pretty printer for a datatype that includes types that already have pretty printers installed or where they will be installed later since it uses any pretty printers for the types when it is actually called.

val prettyPrint: (string -> unit) * int -> pretty -> unit

This function takes an output function and a line length and outputs a pretty structure, interpretting the layout information.