41

I've read through a good chunk of Expert F# and am working on building an actual application. While debugging, I've grown accustomed to passing fsi commands like this to make things legible in the repl window:

fsi.AddPrinter(fun (x : myType) -> myType.ToString())

I would like to extend this to work with the printf formatter, so I could type e.g.

printf "%A" instanceOfMyType 

and control the output for a custom type. The book implies that this can be done (p 93, "Generic structural formatting can be extended to work with any user-defined data types, a topic covered on the F# website"), but I have failed to find any references as to how to actually accomplish this. Does anyone know how? Is it even possible?

Edit:

I should have included a code sample, it's a record type that I'm dealing with, e.g.

type myType = 
    {a: int}        
    override m.ToString() = "hello"

let t = {a=5}
printfn "%A" t
printfn "%A" (box t)  

both print statements yield:

{a = 5;}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
flatline
  • 42,083
  • 4
  • 31
  • 38

3 Answers3

51

It looks like the Right Way to do this in F# 2.0 is by using the StructuredFormatDisplay attribute, for example:

[<StructuredFormatDisplay("hello {a}")>]
type myType = {a: int}

In this example, instead of the default {a = 42;}, you would get hello 42.

This works the same way for object, record, and union types. And although the pattern must be of the format "PreText {PropertyName} PostText" (PreText and PostText being optional), this is actually more powerful than ToString() because:

  1. PropertyName can be a property of any type. If it is not a string, then it will also be subject to structured formatting. Don Syme's blog gives an example of recursively formatting a tree in this way.

  2. It may be a calculated property. So you could actually get ToString() to work for record and union types, though in a rather round-about way:

    [<StructuredFormatDisplay("{AsString}")>]
    type myType = 
        {a: int}
        override m.ToString() = "hello"
        member m.AsString = m.ToString()  // a property that calls a method
    

By the way, ToString() will always be used (even for record and union types) if you call printfn "%O" instead of printfn "%A".

Todd Owen
  • 15,650
  • 7
  • 54
  • 52
  • BTW, credit to @Brian for posting these links in a follow-up comment to his answer. I just thought it was worth fleshing out for anyone else who comes looking. – Todd Owen Nov 23 '12 at 23:58
  • The property/member can be `private` to avoid exposing possibly object allocating object properties (rather than as method invocations). Also, I was unable to make use of object methods ("was not found"). – Henrik Oct 17 '18 at 09:37
5

Hmm... I vaguely recall some changes to this, but I forget if they happened before or after the CTP (1.9.6.2).

In any case, on the CTP, I see that

type MyType() =
    override this.ToString() = "hi"
let x = new MyType()
let xs = Array.create 25 x
printfn "%A" x
printfn "%A" xs

when evaluated in the VFSI window does what I would want, and that

x;;
xs;;

also prints nicely. So, I guess I am unclear how this differs from what is desired?

Brian
  • 117,631
  • 17
  • 236
  • 300
  • Thanks; see my edit to the original post, it's a record type with a member function added, and behaves differently than a class type... – flatline Apr 27 '09 at 00:51
  • 1
    @Brian, yes, that should work, but as flatline says, it doesn't work with union and record types. I ran into this a while ago: http://cs.hubfs.net/forums/post/9163.aspx (can't remember if I sent something to fsbugs when I didn't get any followups, sorry) – Kurt Schelfthout Apr 27 '09 at 05:29
  • See also http://blogs.msdn.com/b/dsyme/archive/2010/01/08/some-tips-and-tricks-for-formatting-data-in-f-interactive-and-a-in-sprintf-printf-fprintf.aspx and http://msdn.microsoft.com/en-us/library/ee370334.aspx – Brian Oct 20 '11 at 06:48
-2

If you override ToString method, that should do.

Codingday
  • 857
  • 6
  • 15