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)

 

producing

 

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]

 

producing

 

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.