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.
from FsPretty
from FsPretty
Full name: Tutorial.mylist
Full name: Tutorial.tuple
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<_>
Full name: Microsoft.FSharp.Collections.List.map
Full name: FsPretty.PrettyPrint.mkint
Full name: FsPretty.PrettyPrint.encloseSep
Full name: FsPretty.PrettyPrint.lparen
Full name: FsPretty.PrettyPrint.rparen
Full name: FsPretty.PrettyPrint.comma
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: FsPretty.Rendering.displayString
Full name: Tutorial.s
Full name: FsPretty.PrettyPrint.text
Full name: FsPretty.PrettyPrint.align
Full name: Tutorial.function_type
Full name: FsPretty.PrettyPrint.punctuate
Full name: FsPretty.PrettyPrint.hcat
Full name: FsPretty.PrettyPrint.parens
Full name: Tutorial.list_type
Full name: Tutorial.argument
Full name: FsPretty.PrettyPrint.colon
Full name: Tutorial.signature
Full name: FsPretty.PrettyPrint.hsep
Full name: FsPretty.PrettyPrint.equals
Full name: Tutorial.fname
Full name: Tutorial.arg1
Full name: Tutorial.arg2
Full name: Tutorial.decl
Full name: Tutorial.body
Full name: FsPretty.PrettyPrint.indent
Full name: tutorial.mapper
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<_>
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<_>
Full name: Microsoft.FSharp.Collections.list<_>