F# Interactive Tips and Tricks: Formatting Data using AddPrinter, AddPrintTransformer and %A in sprintf/printf/fprintf

Mingtian Ni asked the following:

I ‘d like to change the output format for certain types, especially collection types, in fsi. What are the reasonable ways for this? ... Can somebody give a few references here? Or even better with guidelines and working examples.

Here are some tips and tricks for formatting data in F# Interactive. This is not meant to be a comprehensive guide, just enough to get you started. Please let me know if you need more examples.


For F# Interactive, one easy one is to use fsi.AddPrintTransformer to generate a surrogate display object (or use fsi.AddPrinter which is similar, but where you generate a string), e.g.


type C(elems:int list) =

   member x.Contents = elems

   member x.IsBig = elems.Length > 100


let c = C [1;2;3]


fsi.AddPrintTransformer (fun (c:C) -> box c.Contents)




val c : C = [1; 2; 3]


One nice thing about AddPrintTransformer is you can make it conditional, returning null to indicate that the formatter should be skipped:


fsi.AddPrintTransformer (fun (c:C) -> if c.IsBig then null else box c.Contents)


Which is particularly nice if you use it with the “obj” type as you can do very specific custom formatting on any object:


fsi.AddPrintTransformer (fun (obj:obj) -> match obj with 😕 C as c -> box c.Contents | _ -> null)


One problem with this is that fsi.AddPrinter and fsi.AddPrintTransformer don’t modify the behaviour of the %A formats in sprintf, printf etc. For those there is a limited facility to put a simple attribute on a type which names a property generating a surrogate object, along with some surrounding text:


[<StructuredFormatDisplayAttribute("CCC {Contents}")>]

type C(elems:int list) =

   member x.Contents = elems


let c = C [1;2;3]




val c : C = CCC [1; 2; 3]


If your type is a generic collection type, then use a list or sequence as the surrogate object.


If your type is a matrix or table type, then use a 2D array as the surrogate object.


If your type is logically a union type, but you've hidden its representation behind an abstraction boundary, then consider using a separate helper union type which unwraps the structure of your object (i.e. unwraps it one level if your type is a recursive type)


If your data is recursively tree structured you can represent the children as a list:


[<StructuredFormatDisplayAttribute("Tree {Contents}")>]

type Tree(node: int, elems: Tree list) =

   member x.Contents = (node, elems)


let c = Tree (1, [ Tree (2, []); Tree (3, [ Tree (4, []) ]) ])

let c2 = Tree (1, [ c; c])

let c3 = Tree (1, [ c2; c2])


Producing the pleasing:


val c3 : Tree =

  Tree (1,

        [Tree (1,

               [Tree (1, [Tree (2, []); Tree (3, [Tree (4, [])])]);

                Tree (1, [Tree (2, []); Tree (3, [Tree (4, [])])])]);

         Tree (1,

               [Tree (1, [Tree (2, []); Tree (3, [Tree (4, [])])]);

                Tree (1, [Tree (2, []); Tree (3, [Tree (4, [])])])])])


You should generally also consider implementing ToString, and consider adding a DebuggerDisplay attribute if you’re using the VS debugger a lot.


Comments (1)

  1. Thanks for the information!

    I was wondering: I would expect AddPrintTransformer to return an option type instead of null. Or am I missing something?

Skip to main content