FsPretty


Using FsPretty

The goal of a pretty printer combinator library is to make it easy to render structured documents. The combinators provide ways of combining elements of a document together to achieve a layout that is desired without having to manually deal with things like indentation, nesting, alignment, and so on.

For example, say you want to format a list of integers as a tuple:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
#r "FsPretty.dll"
open FsPretty.PrettyPrint
open FsPretty.Rendering

let mylist = [1..6]
let tuple = List.map mkint mylist |> encloseSep lparen rparen comma
printfn "%s" (displayString tuple)

In this case, the list of six integers is first converted to a list of Doc objects via the mkint function. This is handed to the encloseSep function that produces the tuple:

1: 
(1,2,3,4,5,6)

The combinator library also provides functions that help with layout such as aligning text on multiple lines to a column. For example, consider this code that prints one string next to two strings that are stacked above each other with <*>. The align function ensures that the two stacked strings are aligned with respect to the column of their left-most character.

1: 
2: 
let s = text "foo" <+> align ((text "string1") <*> (text "string2"))
printfn "%s" (displayString s)

This prints the string:

1: 
2: 
foo string1
    string2

Pretty printers are often useful when generating code where one wants to avoid messy sprintf() statements or other manual formatting statements. The combinators make it easy to build up layout logic that matches the way you want code laid out. For example, say we want to generate an F# function with types explicitly enumerated in the function definition. We can start off by making some functions to assemble things like function types, list types, function arguments with types specified, and the function declaration itself.

1: 
2: 
3: 
4: 
5: 
6: 
let function_type ts = List.map text ts
                       |> punctuate (text "->")
                       |> hcat |> parens
let list_type t = text t <+> text "list"
let argument name ty = parens (text name <<>> colon <<>> ty)
let signature fname args = hsep [(text "let");(text fname)]::args::[equals]

Once we have this, we can put together a simple function.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let fname = "mapper"
let arg1 = argument "f" (function_type ["int";"float"])
let arg2 = argument "l" (list_type "int")
let decl = signature fname [arg1; arg2]
let body = indent 4 ((text "let x = List.map f l") <*> (text "x"))

printfn "%s" (displayString (signature <*> body))

The result is:

1: 
2: 
3: 
let mapper (f:(int->float)) (l:int list) =
    let x = List.map f l
    x

Using the combinators that take into account alignment, wrapping, and so on, one can build pretty printers that obey expected rules of layout that are challenging to handle manually using print statements and simple string formatting functions.

namespace FsPretty
module PrettyPrint

from FsPretty
module Rendering

from FsPretty
val mylist : int list

Full name: Tutorial.mylist
val tuple : Doc

Full name: Tutorial.tuple
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val mkint : i:int -> Doc

Full name: FsPretty.PrettyPrint.mkint
val encloseSep : left:Doc -> right:Doc -> sep:Doc -> ds:Doc list -> Doc

Full name: FsPretty.PrettyPrint.encloseSep
val lparen : Doc

Full name: FsPretty.PrettyPrint.lparen
val rparen : Doc

Full name: FsPretty.PrettyPrint.rparen
val comma : Doc

Full name: FsPretty.PrettyPrint.comma
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val displayString : s:Doc -> string

Full name: FsPretty.Rendering.displayString
val s : Doc

Full name: Tutorial.s
val text : s:string -> Doc

Full name: FsPretty.PrettyPrint.text
val align : d:Doc -> Doc

Full name: FsPretty.PrettyPrint.align
val function_type : ts:string list -> Doc

Full name: Tutorial.function_type
val ts : string list
val punctuate : p:Doc -> _arg1:Doc list -> Doc list

Full name: FsPretty.PrettyPrint.punctuate
val hcat : (Doc list -> Doc)

Full name: FsPretty.PrettyPrint.hcat
val parens : (Doc -> Doc)

Full name: FsPretty.PrettyPrint.parens
val list_type : t:string -> Doc

Full name: Tutorial.list_type
val t : string
val argument : name:string -> ty:Doc -> Doc

Full name: Tutorial.argument
val name : string
val ty : Doc
val colon : Doc

Full name: FsPretty.PrettyPrint.colon
val signature : fname:string -> args:Doc -> Doc list

Full name: Tutorial.signature
val fname : string
val args : Doc
val hsep : (Doc list -> Doc)

Full name: FsPretty.PrettyPrint.hsep
val equals : Doc

Full name: FsPretty.PrettyPrint.equals
val fname : string

Full name: Tutorial.fname
val arg1 : Doc

Full name: Tutorial.arg1
val arg2 : Doc

Full name: Tutorial.arg2
val decl : Doc list

Full name: Tutorial.decl
val body : Doc

Full name: Tutorial.body
val indent : i:int -> d:Doc -> Doc

Full name: FsPretty.PrettyPrint.indent
val mapper : f:(int -> float) -> l:int list -> float list

Full name: tutorial.mapper
val f : (int -> float)
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val l : int list
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
val x : float list
Fork me on GitHub