Brian's profileInside F#BlogGuestbookNetwork Tools Help

Blog


    May 17

    DebuggerVisualizers in F#

    One of my very favorite features of Visual Studio is debugger visualizers.  These let you pop up a custom display of values, like those values that appear in the "locals" window, for example.

    The F# team has not yet invested much effort in the F# debugging experience.  One thing that needs improvement is the appearance of values in the locals window.  For example, look at how these list variables ("lc" and "li") appear by default:

    DebugDefault

    The "Value" in the locals window at the bottom just displays as "{Microsoft.FSharp.Collections.List<char>._Cons}", for instance.  That doesn't help much when you're debugging your program - typically you want to see the values in the list (e.g. "['a'; 'b'; 'c']").

    Though we'll undoubtedly improve the out-of-the-box story here in a future release of F#, the current situation gives me a good excuse to talk about debugger visualizers.  When you install a visualizer, a little magnifying-glass icon appears next to values, along with a drop-down menu that lets you select among the visualizers.  In today's blog entry, I'll show how to install a "list visualizer" which can be selected in the debugger as suggested in the screenshot below (note the "FSharp List Visualizer" drop-down near the very bottom of the picture):

    DebugVizMagnifyingGlass

    When selected, it will pop up a dialog box that displays the list contents:

    DebugViz

     

    Installing the debugger visualizer

    For those of you who just want the install instructions to try this out, I'll summarize them:

    From the Visual Studio "Tools\Options" menu, under "Debugging\General", uncheck "warn if no symbols on launch" (see screenshot below).

    Build the F# code that appears at the end of this blog into an exe.  Then copy the exe into the VS debugger visualizers directory, using a command-line like:

    copy MyDebugViz.exe "c:\Program Files\Microsoft Visual Studio 9.0\Common7\Packages\Debugger\Visualizers"

    Close Visual Studio.  Re-open Visual Studio.

    That's it - now you're ready to go.  Just put a breakpoint on the "printf" line as suggested by the screenshot below, and you should see the magnifying glass icon in the locals window next to list values, as in the previous screenshots.

    DebugOptions

     

    Authoring debugger visualizers

    Though the documentation for visualizers is a little sparse, simple visualizers are pretty easy to write.  I won't go into all the nitty gritty details, but I'll give the high-level overview.  There are three main parts. 

    First, there is the "object source", which runs in the debuggee (the process being debugged).  It is used to serialize "some data about the object we want to debug" into a stream which we can send to the debugger.  Here's an example, that uses <sprintf "%A"> as a way to pretty-print an F# value to a string, which is then written to a stream.

    type FSharpObjectSource() =
        inherit VisualizerObjectSource()
        override this.GetData(target : Object, outgoingData : System.IO.Stream) =
            let formatter = new BinaryFormatter()
            formatter.Serialize(outgoingData, sprintf "%A" target)
            ()

    Next, there is the "debugger visualizer", which runs in the debugger, and receives the data from the debuggee via an "object provider".  It can display the information it receives in a dialog box.  In the code below, we receive our string data, and then just create a Form with a RichTextBox to display the data.

    type FSharpDebuggerVisualizer() =
        inherit DialogDebuggerVisualizer()

        override this.Show(windowService : IDialogVisualizerService,
                           objectProvider : IVisualizerObjectProvider) =
            let data = objectProvider.GetObject()
            use displayForm = new Form()
            let text = new RichTextBox(Dock = DockStyle.Fill,
                                       Text = data.ToString(),
                                       Font = new Font("Lucida Console",10.0f,FontStyle.Bold),
                                       ForeColor = Color.DarkBlue)
            displayForm.Controls.Add(text)
            displayForm.Text <- "F# list visualizer"
            windowService.ShowDialog(displayForm) |> ignore

    Finally, there is an attribute which associates an object source and a visualizer with a specific data type and gives it a name:

    [<assembly:DebuggerVisualizer(typeof<FSharpDebuggerVisualizer>,
                                  typeof<FSharpObjectSource>,
                                  Target = typedefof<Microsoft.FSharp.Collections.List<Object> >,
                                  Description = "FSharp List Visualizer")>]

    The "Target" here says that this visualizer will work on any variable with type list<'a> (in the CLR, the type we want to target is called "List`1" - the uninstantiated-generic type for List, and this is what the F# "typedefof" operator yields).  The "Description" is the text that appears in the drop-down menu next to the magnifying glass icon (as in the second screenshot in today's blog entry).  And the two "typeof" arguments just point to the classes that contain the code that should run in the debugger and debuggee when this visualizer is invoked.

    That's pretty much all there is to it.  If you find yourself often debugging some code that uses data that doesn't display well naturally in the debugger, visualizers are a great way to extend the debugger to provide your own visualization of the data in your running program.

    The source code

    Here's the code:

    #light

    namespace FSharpDebuggerVisualizers

    #r "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.DebuggerVisualizers.dll"

    open System
    open System.Diagnostics
    open System.Runtime.Serialization.Formatters.Binary
    open System.Collections.Generic
    open System.Windows.Forms
    open System.Drawing
    open Microsoft.VisualStudio.DebuggerVisualizers

    type FSharpObjectSource() =
        inherit VisualizerObjectSource()
        override this.GetData(target : Object, outgoingData : System.IO.Stream) =
            let formatter = new BinaryFormatter()
            formatter.Serialize(outgoingData, sprintf "%A" target)
            ()

    type FSharpDebuggerVisualizer() =
        inherit DialogDebuggerVisualizer()

        override this.Show(windowService : IDialogVisualizerService,
                           objectProvider : IVisualizerObjectProvider) =
            let data = objectProvider.GetObject()
            use displayForm = new Form()
            let text = new RichTextBox(Dock = DockStyle.Fill,
                                       Text = data.ToString(),
                                       Font = new Font("Lucida Console",10.0f,FontStyle.Bold),
                                       ForeColor = Color.DarkBlue)
            displayForm.Controls.Add(text)
            displayForm.Text <- "F# list visualizer"
            windowService.ShowDialog(displayForm) |> ignore

    namespace Global

    open System
    open System.Diagnostics
    open FSharpDebuggerVisualizers

    [<assembly:DebuggerVisualizer(typeof<FSharpDebuggerVisualizer>,
                                  typeof<FSharpObjectSource>,
                                  Target = typedefof<Microsoft.FSharp.Collections.List<Object> >,
                                  Description = "FSharp List Visualizer")>]
    do
        let li = [1; 2; 3]
        let lc = ['a'; 'b'; 'c']
        printfn "%A %A" li lc

        printfn "press a key to quit"
        System.Console.ReadKey() |> ignore

    Comments (6)

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.
    Brian McNamara has turned off comments on this page.
    The "immediate window" is your friend (though it still uses C# syntax).  Check out this screenshot for a suggestion.
    2 July
    Thanks for your quick answer.

    It's very nice that F# will be fully integrated in Visual Studio. But how am I going to debug my applications until then?
    Is it possible to attach F# interactive to your debugging session, so that I can use printfs to inspect variables etc? (This would make debugging very powerful in general). Or do you have any other hints on F# debugging?

    Unfortunately VS is not open source, for otherwise I would hack into it myself ;-)
    30 June
    Good question.  You can't use typedefof<Object> as a Target in a DebuggerVisualizer (by design of DebuggerVisualizers).  This is unfortunate, as since Union types do not have a common base class there is no way to make visualizers "for all Unions".  However we intend to have a better default debugging experience for union types in a future release of F#, so hopefully this will be less important after that.
    28 June
    Thanks for this nice article.
    This really makes debugging F# apps easier while there is no support in default by F#.
    What would be nice IMO is to have viewers for all the common F# types.

    I tried to create a viewer using an annotation like:

    [<assembly:DebuggerVisualizer(typeof<FSharpDebuggerVisualizer>,
                                  typeof<FSharpObjectSource>,
                                  Target = typedefof<Object>,
                                  Description = "FSharp List Visualizer")>]

    to have the viewer available for all Objects but that doesn't seem to work for Union types. Do you know anything more here?
    27 June
    Picture of Anonymous
    Nick Black wrote:
    Fantastic! This kind of thing has me really excited [0] about IDE research, the vast majority of which does seem to be coming out of that little-known-company in Redmond. My question upon the first capture was "so do they expose the language model and let me extend these", immediately answered in the affirmative...I hope this kind of thing finds its way into modern language design beyond F# (which looks grand, by the way -- well done to you and your team).

    Does F# grow at all from the inspired work that was FC++ (in case I don't read any further before you reply)?

    Good to see you're keeping it real, Brian! This looks like really, really fun work.

    [0] (I'm less thrilled about the Windows Live Gay Spaces account required to comment. C'est la vie!)
    18 May
    I should add that you can use DebuggerDisplayAttribute to change what appears in the Value field.  For example, add this code right above the existing [<assembly:...>] attribute and watch what happens:

    module ListMethods =

        let AsPrettyString(l : list<'a>) = sprintf "%A" l

    [<assembly:DebuggerDisplay("{Global.ListMethods.AsPrettyString(this)}",

        Target = typedefof<Microsoft.FSharp.Collections.List<Object> >)>]

    18 May

    Trackbacks

    Weblogs that reference this entry
    • None