CHAPTER 12

The F# Tool Suite and .NET
Programming Tools

This chapter will be a little different from most of the chapters in this book; instead of focusing on examples of F# programs, it'll focus on how to use various programming tools, both those that are distributed with F# and those that target .NET in general.

The F# distribution includes two versions of the compiler and a number of other tools. These are all available in the distribution's in directory. You can find the F# compiler, at the time of writing, in c:Program FilesFSharp<version>in where <version> is the version number of F# that you have installed. This chapter will give a quick tour of the useful tools in this directory.

Specifically, I'll cover the following:

fsc.exe The F# compiler

fsi.exe F# interactive, which is an interactive version of the compiler

fslex.exe A tool for creating lexical analyzers

fsyacc.exe A tool for creating parsers

resxc.exe A resource file compiler

First, you'll take a closer look at the various command-line switches for fsc.exe. Next, you'll examine various ways you can use fsi.exe more effectively.

Using Useful fsc.exe Command-Line Switches

You can view the basic F# command-line options using the -help switch; I describe them in the section "Basic Compiler Switches." F# also has a large number of advanced command-line switches; you can view them using the --full-help command-line flag. You don't need to know all of them for your everyday F# programming. A lot of them are just for using the compiler in experimental ways, so I won't document them here. Don't think this means you shouldn't use the switches that aren't documented, but if you do use them, then carefully test any resulting assembly before it is released. I've grouped the nonexperimental switches by functional area, and I'll describe them in the rest of this chapter.

Basic Compiler Switches

F# offers a number of basic command-line switches that do everything you'd expected a compiler to be able to do. I summarize them in Table 12-1.

Table 12-1. Basic F# Compiler Switches

Switch Description
-o <string> This controls the name of the assembly that will be produced.
-a This produces an archive, a .dll, rather than an executable. You can use the advanced command-line options that start with --target to get more fined-grained control over this.
-r <string> This is the filename of a .NET assembly to reference so types and methods can be used from the assembly. If a full file path is given, then this is used as is; if just the filename or a relative path that is given, then the current directory, the F# binaries directory (usually c:Program FilesFSharp-<version>in), and the framework directory (usually c:WINDOWSMicrosoft.NETFrameworkv<version>) are searched for the assembly. You can add directories to this search path by using the -I switch described in this table. If no assembly is found matching the given name, an error is raised, whether the input source files are valid or not.
-R <string> This is the same as -r, except that the assembly being referenced is copied locally. This is useful because it means the .NET loader will be able to find the assembly when it is run.
-I <string> This specifics a directory that will be used in the search for assemblies when they are referenced with the -r or -R flag.
-g This produces a symbol file, a .pdb, that will allow you to set breakpoints and step through the source line by line in a debugger. This also turns off all optimizations, unless you give one of the optimizations flags (flags that begin with -O).
--define <string> This defines a symbol for conditional compilation, a technique that you can use to exclude source code from compilation. I discuss this technique further in Chapter 6.
-i This prints the inferred interface of a file to the console so that you can see what types have been inferred by the compiler for your values. This is useful for creating signature files, which I discuss further in Chapter 6.
-doc <string> This writes the doc comments for the assembly to the given file. Doc comments are a special type of comment and are intended to create documentation for programmers who will use the finished assembly. I discuss them further in Chapter 6.

Compiler Optimization Switches

The compiler optimization switches are listed among the basic command-line options when you use the -help command-line option. I recommend that you compile code using the optimization switches when you compile your code for release, because compiler optimizations can significantly increase the performance of your code. Table 12-2 summarizes the optimization switches.

Table 12-2. Optimization F# Compiler Switches

Switch Description
-Ooff This turns off all optimizations including those performed by the .NET Framework's JIT compiler.
-O0 This enables optimizations by the JIT compiler but turns off all optimizations by the F# compiler.
-O1 This enables optimizations by the JIT compiler and optimizations that are local to an F# module.
-O This is the same as -O2; it is the default unless you specify that debugging symbols should be produced (by using the -g flag).
-O2 This is the same as -O1 except that optimizations between F# modules are also allowed.
-O3 This is the same as -O2 but with increased inlining and lambda lifting.

I took the OCaml "Spectral Norm" benchmark from the Computer Language Shootout Benchmarks site (http://shootout.alioth.debian.org/) and ported it to F#. You can find information about what a spectral norm is at http://mathworld.wolfram.com/SpectralNorm.html. Here's the code used to do the benchmark:

#light
let evalA i j = 1.0 / float((i+j)*(i+j+1)/2+i+1)

let evalATimesU u v =
    let n = Array.length v - 1
    for i = 0 to  n do
        v.(i) <- 0.0
        for j = 0 to n do
            v.(i) <- v.(i) + evalA i j * u.(j)

let evalAtTimesU u v =
    let n = Array.length v −1 in
    for i = 0 to n do
        v.(i) <- 0.0
        for j = 0 to n do
            v.(i) <- v.(i) + evalA j i * u.(j)

let evalAtATimesU u v =
    let w = Array.create (Array.length u) 0.0
    evalATimesU u w
    evalAtTimesU w v

let main() =
    let n =
        try
            int_of_string(Sys.argv.(1))
        with _ ->  2000

   let u = Array.create n 1.0
   let v = Array.create n 0.0
   for i = 0 to 9 do
       evalAtATimesU u v
       evalAtATimesU v u

   let vv = ref 0.0
   let vBv = ref 0.0
   for i=0 to n-1 do
       vv := !vv + v.(i) * v.(i)
       vBv := !vBv + u.(i) * v.(i)

   Printf.printf "%0.9f " (sqrt(!vBv / !vv))

main()

I then compiled this into a number of different executables, differing only by optimization level, and I timed the execution of these programs using ntimer.exe, which is available with the Windows Server 2003 Resource Kit. The times shown in Table 12-3 are all in seconds, and the "Percentage Diff" number is the percentage change from the unoptimized time.

Table 12-3. Times from the Spectral Norm Benchmark

Optimization Level Command Line Kernel User Total Percentage Diff
-Ooff ntimer spectral-Ooff.exe 2000 00.090 03.535 03.715 0
-O0 ntimer spectral-O0.exe 2000 00.080 03.525 03.725 −0.27
-01 ntimer spectral-O1.exe 2000 00.080 02.954 03.174 17.0
-02 ntimer spectral-O2.exe 2000 00.030 02.984 03.154 17.9
-03 ntimer spectral-O3.exe 2000 00.050 03.214 03.394 9.5

Although the time difference might look relatively small, there is actually a 17.9 percent difference between the fastest time and the unoptimized time. Although it's difficult to predict what effect these flags would have on other programs, and particularly on user perception of response time, a 17.9 percent increase in execution speed is not insignificant, and it's worth using these switches since they can give performance gains for such little effort.

Compiler Warning Switches

The compiler generates warnings to let you know when it thinks you've done something you didn't mean to, such as initializing a private identifier or not using it within the module in which it's defined. Unlike errors that cause the compilation to fail, when the compiler produces only warnings, it will still compile the code. Table 12-4 summarizes the warning switches.

Table 12-4. Warning F# Compiler Switches

Switch Description
--all-warnings This flag means the compiler will print all the warnings it finds with the source code.
--no-warnings This means the compiler will not print any warnings; it is generally not advisable to use this flag.
--all-warnings-as-errors This means that any warning will be treated as an error, meaning that an assembly will not be produced. This is useful to stop yourself from getting lazy and ignoring warnings; if left unchecked, warnings can quickly get out of control on large projects, especially if they are shared between many programmers.
--warn-as-error <int> This is a lesser form of the --all-warnings-as-errors flag, allowing you to treat a specific warning as an error.
--warn <int> This informs the compiler to warn you when it finds a specific warning; this is useful in conjunction with the --no-warnings flag.
--no-warn <int> This informs the compiler to not warn you about a specific warning; this is useful when there are mitigating circumstances for a warning appearing in your code; however, you should not use it without careful consideration.

Compiler Target Switches

These flags give you fine-grained control over what the compiler produces. These are most useful when producing a WinForms application that does not need to write to the console. Table 12-5 summarizes the target switches.

Table 12-5. Target F# Compiler Switches

Switch Description
--target-exe This produces an executable assembly designed to execute within the window's console; if you execute it outside the console, it will pop up its own console, even if the application uses WinForms components.
--target-winexe This produces an executable assembly that does not have a console associated with it; usually you will use this flag when you create WinForm applications in F#.
--target-dll This produces a .dll file.
--target-module This produces a binary file that is a module rather than an assembly; several modules can be composed into a multifile assembly using tools distributed with the .NET SDK. However, this functionality is not used very often.

Signing and Versioning Switches

Assemblies must be cryptographically signed and have a version number before they can be installed in the GAC. Assemblies are signed with keys produced by the sn.exe tool, distributed with the .NET SDK. Signing an assembly also gives you some level of confidence that the assembly has not been tampered with after it left its creator; since anyone can create a strong name key, this does not tell you anything about who the creator was. Adding a version number of an assembly is most useful for the producers of libraries, because it allows the users of your library to better track changes to it and to decide which of their applications will upgrade to the new version when it is released. Table 12-6 summarizes the signing switches.

Table 12-6. Signing F# Compiler Switches

Switch Description
--keyfile <string> This tells the compiler to sign the assembly with the key that it finds in the given key file.
--public-keyfile <string> This tells the compiler to sign the assembly with a key file that contains only a public key. This is a process known as delayed signing; by signing with the public key, it allows many developers to work on an assembly while keeping the private key safe, limited to a privileged few. The assemblies produced will run a machine only where the CLR has been told to skip verification for the specific key. This can be achieved using the sn.exe tool.
--version <string> This sets the version number of an assembly; the format of the string is <major version>.<minor version>.<build number>.<revision number>, resulting in a string like 2.1.53.3. If this flag is not set, then it defaults to 0.0.0.0.
--version-file <string> This sets the version number the same way as the --version flag does, but it takes the version number for a text file. This is useful if you intended to increment your file number for each build, keeping track of it via a file under source control.


Note You can find more information about the sn.exe tool, which is used to create the key files, at http://msdn2.microsoft.com/en-us/library/k5b5tt23(VS.80).aspx.


Printing the Interface Switches

The -ifile <string> flag prints the inferred interface of an assembly the same way that -i does, but it prints it to a file rather than to the console.

Adding Resources Switches

A resource is something that is embedded in an assembly. It can be one of several different things. It might be a string that will be displayed to the user, or it might be an image, icon, video, music file, or any sort of binary data. Resources can help make application deployment easier. For example, if your application needs to display an image to the user, embedding it in the assembly as a resource will mean that it is always there when your code needs to use it, and you do not need to worry about deploying it along with your assembly.

Resources can be divided into two groups, Win32 resources and .NET resources. Win32 resources are created using the resource compiler (http://msdn2.microsoft.com/en-us/library/aa381042.aspx), which allows the user to define resources in a C++-like language that is stored in a text file with the extension .rc. Although you can use these resource files to define lots of different types of resources, it is generally best to use them just for storing icons, because it is generally easier to store and access your resources using .NET resource files. However, you can use embedded Win32 icons to control what the resulting assembly files look like in Windows Explorer. .NET resources are either text files that have the normal .txt extension or XML files that have the extension .resx. The latter can be included on the F# command line directly or can alternatively be converted into a binary .resource format by using the .NET resources generator, resgen.exe (http://msdn2.microsoft.com/en-us/library/ccec7sz1(VS.80).aspx), or the resxc.exe tool distributed with F#. .NET resource files have a number of advantages over Win32 resource files. The .NET file format is much easier to understand and work with, and also Visual Studio provides some nice resource management tools. It is also much easier to localize your applications, making them available in different languages, with .NET resource files. Table 12-7 summarizes the resource switches.

Table 12-7. Resource F# Compiler Switches

Switch Description
--win32res <string> This specifies a file that should be embedded into the assembly as a Win32 resource.
--resource <string> This embeds the specified .NET .resource file in the assembly. A .resource file is created using the tool resgen.exe distributed with the .NET SDK or the tool resxc.exe distributed with F#. Note that you can also give a .resx file directly as a source input to the F# compiler, and it will invoke resxc.exe for you.
--link-resource <string> This is the same as the --resource flag but gives control over the name of the embedded resource file and whether it is public or private. The format of the string passed to this flag is <filename>, <resource name>, <public | private>. The <resource name> and <public | private> fields are optional strings such as res.resource, AssemblyResources or res.resource, AssemblyResources, private.


Note You can find more information about the format of .resx files, which are used to produce managed .resource files, at http://msdn2.microsoft.com/en-us/library/ekyft91f.aspx.


Generating HTML Switches

You can use the compiler's -doc switch to place doc comments, described in Chapter 6, into an XML file. Tools such as NDoc or Microsoft's Sandcastle can then turn these into different documentation formats. Although F# ultimately produces .NET code, its type system is, practically speaking, more expressive than the .NET type system and uses .NET constructs in powerful ways; therefore, under some circumstances, these tools do not always do a good job producing documentation for F# assemblies. That is why the compiler provides a set of switches to produce HTML documents directly. Table 12-8 summarizes the HTML documentation switches.

Table 12-8. HTML Documentation F# Compiler Switches

Switch Description
--generate-html This flag will make the compiler output HTML documentation for the assembly.
--html-output-directory <string> This flag allows you to specify the directory to which the HTML documentation is output.
--html-css <string> This flag allows the user to specify a path to a CSS file that will be automatically embedded into the headers of each resulting documentation file.
--html-namespace-file <string> This allows the user to specify the name of a file that the namespaces within the assembly should summarize, creating an index of all the types and modules within the assembly.
--html-namespace-file-append This specifies that the summary of the namespace in an assembly should be appended to a file, rather than overwriting it. This is useful if you are aiming to produce a set of two or more libraries.

The choice about whether you should use these flags to document your code or whether you should use a tool that targets .NET is usually dictated by the type of library you produce. If you want a library that can be easily used only from F#, then you should use these command-line tools. If your aim is to produce a library that can be used easily from any language, then you'll probably get better results using Sandcastle or NDoc. You can find a more detailed explanation of why these two types of libraries exist and how you can create them in Chapter 13.

CLI Version Switches

Because the .NET Framework has multiple versions, it's highly likely that you will have multiple versions installed on your machine; typically, most users will have versions 1.1 and 2.0 installed. The story can be even more complicated because various implementations of the CLI standard exist, such as Mono and SSCLI (Rotor), meaning these versions of the CLI (or even customized builds of these versions) could also be installed.

These two flags allow you to control exactly which version of the CLI is used; this is important because there are variations in the types and methods that exist in the different versions. This means a program that will compile one version may not compile with another. It is therefore important that the programmer has control over which version is used. Table 12-9 summarizes the CLI version switches.

Table 12-9. CLI Version F# Compiler Switches

Switch Description
--cli-version <string> This flag controls the version number of the CLI that is used. It can take the values 1.0, 1.1, and 2.0 and custom build tags such as v2.0.x86chk. You may need to use the flag --clr-root to direct the compiler to the right version of the CLI; typically you need to use this when you are using a custom-built CLR.
--clr-root <string> This directs the compiler to the framework directory, where the libraries for that particular version of the framework can be found.

Compilation Details Switches

The --progress flag shows the progress of the compiler to let you know a little about what is going on inside the compiler.

Statically Linking Switches

Static linking is the process where methods and types from referenced assemblies are copied and embedded directly into an assembly. This removes the dependency on the referenced assembly. Table 12-10 summarizes the linking switches.

Table 12-10. Linking F# Compiler Switches

Switch Description
--standalone This will place all the types and values from any F# library DLL, or any referenced DLL that transitively depends on an F# library DLL, into the assembly being produced. For this process to work effectively, the program being compiled must not expose any F# types on its interface; for example, this means a public function should not return a tuple because this is an F# type.
--static-link <string> This will statically link any assembly referenced, not just F# library DLLs. The given string should be the name of the assembly without its extension, so it's MyLib, not MyLib.dll.

Using fsi.exe Effectively

The interactive version of F#, fsi.exe, allows you to execute code as you type it into the console. The following sections will look at the commands that were added to F# to aid users working with the console, cover the command-line switches it supports, and give some general tips for working with F# interactive.

fsi.exe Commands

Because of fsi.exe's dynamic nature, you need to perform some tasks with special commands that you would ordinarily use command-line switches for with the fsc.exe compiler. There are also some features, such as automatically timing program execution and quitting the compiler, that just aren't relevant to the command-line compiler. Table 12-11 describes the fsi.exe commands.

Table 12-11. The F# Interactive Commands

Command Description
#r "<assembly file>";; This allows an assembly to be referenced by fsi.exe, meaning that programs created within the console can use their types and values. It has the same meaning as the compiler's -r flag.
#I "<file path>";; This adds a directory to the list of directories that are searched when looking for referenced assemblies. If a filename, or a relative file path, is used when using the #r command, then this list of directories will be searched for the assembly.
#use "<source file>";; This loads, compiles, and runs a single F# source file as if it had been typed into the console directly.
#load "<source file>" ... "<source file>";; This loads and compiles a series of F# source files, as if they were to form a single assembly file. Commands from the sources are not executed immediately, but the types and values they contain are available in the console session.
#time;; This toggles on and off the timing mode of F# interactive.
#types;; This is used to control whether types are displayed.
#quit;; This exits F# interactive; it has a short form of #q;;.


Note The Ctrl+C combination cannot be used to quit F# interactive because Ctrl+C is used to abort long-running computations.


Controlling the fsi.exe Environment

One of the most useful features of fsi.exe is its ability to print values, meaning that you can more easily see the contents of lists, arrays, or any IEnumerable collection. For example, you might want to see the assemblies that are currently in memory, and you could do that by typing the following program into fsi.exe:

> open System;;

> AppDomain.CurrentDomain.GetAssemblies();;

When entered, the program will start with the following output and carry on for many hundreds of lines:


val it : Assembly []
= [|mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   {CodeBase = "file:///C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/mscorlib.dll";
    EntryPoint = null;
...

So much information is shown because fsi.exe automatically prints the values of any properties it finds too. Although this can be useful sometimes, it can be undesirable because it can lead to too much information being shown, as demonstrated with the previous program. To give the user fine-grained control over what actually should be shown, fsi.exe provides the special value fsi of type InteractiveSession, which can be used to control the fsi.exe environment. The easiest way to see what you can do with the fsi values is simply to type fsi;; into the F# interactive console, which gives the following output:


val it : InteractiveSession
       = Microsoft.FSharp.Compiler.Interactive.InteractiveSession
           {EventLoop = Microsoft.FSharp.Compiler.Interactive.Shell+main@1283;
            FloatingPointFormat = "g10";
            FormatProvider = ;
            PrintDepth = 100;
            PrintIntercepts = null;
            PrintLength = 100;
            PrintWidth = 78;
            ShowIEnumerable = true;
            ShowProperties = true;}

All the properties of fsi are mutable so can be set to control the environment. In the previous example, too much information was shown because the properties of each value were printed; you could correct this by using the following command:

> fsi.ShowProperties <- false;;

So, rerunning the previous example would now result in the following output, a much more manageable amount of information:


val it : Assembly []
= [|mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;
    fsi, Version=1.1.13.8, Culture=neutral, PublicKeyToken=null;
    fslib, Version=1.1.13.8, Culture=neutral, PublicKeyToken=a19089b1c74d0809;
    System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;
    mllib, Version=1.1.13.8, Culture=neutral, PublicKeyToken=a19089b1c74d0809;
    FSharp.Compiler, Version=1.1.13.8, Culture=neutral,
         PublicKeyToken=a19089b1c74d0809;
    System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
         PublicKeyToken=b77a5c561934e089;
    System.Drawing, Version=2.0.0.0, Culture=neutral,
         PublicKeyToken=b03f5f7f11d50a3a;
    FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;
    FSharp.Interactive.Settings, Version=1.1.13.8, Culture=neutral,
         PublicKeyToken=a19089b1c74d0809|]

Table 12-12 summarizes the members of the fsi value.

Table 12-12. The Properties of the F# Interactive Command's fsi Value

Property Description
EventLoop This gives access to F# interactive's event loop, which is the thread that takes care of any forms that are currently shown.
FloatingPointFormat This is a string that controls the format in which floating-point numbers are printed.
FormatProvider This an instance of the standard .NET interface System.IFormatProvider.
PrintDepth This is the number of levels of inner lists or properties that will be printed.
PrintIntercepts This is a list of functions that will be executed on items that are printed, before they are printed. This gives the user very fine-grained control over what is printed. See the functions AddPrinterTransformer and AddPrinter for more details.
PrintLength The number of items in any collection type that will be printed.
PrintWidth The number of characters that will be printed on each line before automatically wrapping.
ShowIEnumerable This controls whether IEnumerable collections will be printed.
ShowProperties This controls whether properties should be shown.
AddPrintTransformer This adds a print transformer, which is a function that will be called on an object to transform it before it is printed. This function takes a function of type 'a -> obj; the function is executed only on types that match the type of the parameter of the function.
AddPrinter This adds a printer, which is a function that will be called to get the output that should be printed for an object. It differs from a print transformer because the function is directly responsible for creating text that will be printed, whereas a printer transformer transforms the object to be printed into something more relevant to be printed. This function takes a function of type 'a -> string, and the function is executed only on types that match the type of the parameter of the function.

fsi.exe Command-Line Switches

Table 12-13 summarizes the command-line switches that you can use with fsi.exe.

Table 12-13. The F# Interactive Command-Line Switches

Switch Description
--gui This creates a GUI loop so that the fsi.exe user can open WinForms windows. There is a script, load-wpf.fsx, available as part of the samples in the F# distribution that shows how to replace the WinForms event loop with WPF so WPF applications will run correctly interactively. You can find more information about WPF in Chapter 8.
--no-gui This turns off the GUI loop required for a WinForms application.
--exec This causes fsi.exe to exit after running the scripts given on the command line, which is useful for using F# to execute finished scripts.
--no-logo This stops the splash text being shown on start-up.
--no-banner This is the same as --no-logo.
--no-readline This stops attempts to process individual keystrokes from the console.

Using the Source Directory Macro

The source directory macro is a #define macro with the name __SOURCE_DIRECTORY__ automatically set to the directory for each file being processed by fsi.exe and to the current directory for a script fragment being loaded into fsi.exe (including fragments loaded interactively using Visual Studio). You could use this to access image files that are required for the script and are stored in the same directory as the script.

You can use the identifier __SOURCE_DIRECTORY__ as if it were a string inside any F# fsi.exe script. The following example shows it being used to create a DirectoryInfo object that could then be used to find out what files that directory contains:

#light
open System.IO
let dir = new DirectoryInfo(__SOURCE_DIRECTORY__);;

Writing NUnit Tests

NUnit is an open-source framework for creating NUnit tests for .NET code. The idea is loosely based on JUnit, a Java open source framework. The idea has been popular amongst the .NET development community, and a similar framework is now also included in the Team Editions of Visual Studio 2005.

The idea behind NUnit is simple; you create a .NET class that is a suite of unit tests for your code. Ideally each test will call the functions that you have created with a number of different parameters, asserting that each function returns the expected result. The class and class members are then marked with attributes that show they represent a test. NUnit then provides a framework for running your tests, either through a GUI so programs can easily see the results of their test and drill down on any that are failing or through a command-line tool so the test can be automated as part of a build process.

The following example shows a small library and a unit test suite associated with it. Notice how the test suite, the class TestCases, is marked with the custom attribute TestFixture, and all its members are marked with the custom attribute Test. These custom attributes are both defined in the assembly NUnit.Framework.dll. This is so NUnit knows that this class is a test suite. The assembly can contain other types that are test suites, and equally the class TestCases can contain other members that are not directly test cases but are, for example, helper functions. It would be more natural to separate the code for the test cases from the code being tested into separate files and even separate assembles, but for simplicity I'll show them both together.

Each test case typically calls the function it is testing and then uses the Assert class to check the result. This is not true for the TestDiv02 case; here you know that calling div with a second argument of 0 will cause the function to raise an exception, so you mark the method with the ExpectedException attribute instead of making an assertion.

#light
open System

let add x y = x + y
let div x y = x / y

open NUnit.Framework

[<TestFixture>]
type TestCases = class
    new() = {}
    [<Test>]
    member x.TestAdd01() =
        Assert.AreEqual(3, add 1 2)
    [<Test>]
    member x.TestAdd02() =
        Assert.AreEqual(4, add 2 2)
    [<Test>]
    member x.TestDiv01() =
        Assert.AreEqual(1, div 2 2)
    [<Test; ExpectedException(type DivideByZeroException)>]
    member x.TestDiv02() =
        div 1 0 |> ignore
end

You could load this test case into the NUnit GUI, allowing you to call each test individually or all the tests together. Figure 12-1 shows the NUnit GUI in action, with the TestDiv01 case being run.

image

Figure 12-1. The NUnit GUI

Using Assembly Browsers

Because of all the metadata built into .NET assemblies, it is a fairly easy task to reflect over an assembly to determine its contents. Several class browsers are available that take advantage of this to let developers see the contents of an assembly. The .NET Framework SDK includes a tool called ildasm.exe that lets you browse the contents of an assembly and even look at the IL bytecodes that make up the method bodies. Visual Studio also ships with a .NET class browser that allows you to browse classes and view the signatures of their methods.

However, the best class browser in my opinion is Reflector, which is shown in Figure 12-2 and available for download from http://www.aisto.com/roeder/dotnet/. Reflector lets you browse a number of different assembles at once and provides an easy way to navigate between related types. It also allows you to view the method signatures, and even the code itself, in a variety of different languages. At the time of this writing, IL, C#, VB .NET, and Delphi were supported by default with the option to add others through a plug-in system; currently, a plug-in to view code in F# is in the early stages of development.

Although looking at the code that makes up an assembly is fun, there are some serious uses for the F# user. If you intend to produce a library that is suitable for use from other languages, it is likely that your target audience will consist of a lot of C# and VB .NET developers. If you want them to be able to use the library easily, it is important to know what the method signatures will look like in C# or VB .NET. Although after a while you'll have a good idea of what will play nicely in other languages and what won't, Reflector can help shortcut this by allowing you to view the method signature and check that it looks OK. You can find more about how to create a .NET library that will work well when used from other languages in Chapter 13.

image

Figure 12-2. Reflector, a class browser

Using Debugging Tools

Visual Studio provides a graphical debugger that is easy and intuitive to use. If you have F# integration installed, then debugging is simply a matter of setting a breakpoint and pressing F5. However, not everybody uses Visual Studio; if you don't, several other debugging options are available.

The .NET Framework SDK comes with two command-line debuggers, mdbg.exe and cordbg.exe, but personally I find command-line debuggers too difficult to use. Fortunately, it also comes with a graphical debugger. The debugger is located by default in SDKv2.0GuiDebug, under the install root of the SDK. This debugger, shown in Figure 12-3, is also simple to use. You generally open a source file, set breakpoints within it, and then use the Tools image Attach to Process menu option to choose the program you want to debug. If the program has debugging symbols (generated by the -g option) and the debugger can find them, then your breakpoints will be hit, and you can step through the code. A good way to check whether symbols are loaded is through the Modules windows (Debug image Windows image Modules). This shows all the DLLs that are currently loaded into the process and whether they have debugging symbols associated with them. If no symbols are associated with a particular DLL, then you can try to load some by right-clicking and searching for the correct symbols on the disk.

image

Figure 12-3. Debugging using the GUI debugger available with the .NET Framework SDK

Some sections of code can be difficult to debug because it is impossible to attach the debugger before they've executed. To allow these sections to be debugged more easily, .NET provides the System.Diagnostics.Debugger class. This has a useful method called Launch(). When this method is hit, it will generate a special type of exception that will cause Windows to show a dialog box offering the user the opportunity to attach a debugger. Once attached, the debugger will function normally, and you'll be able to step through the code as you'd expect.


Note Another option for debugging on the Windows platform is WinDbg. This is a tool originally targeted at unmanaged code, but it has been extended to managed code, such as F# programs, via SOS.dll. WinDbg is quite a bit harder to use than your typical graphical debugger, but it has the advantage that it can be used to monitor software in production and thus investigate any production problems you have. You can find more information about how to set up WinDbg at http://strangelights.com/FSharp/Foundations/default.aspx/FSharpFoundations.WinDbg. Also, if you release your software and an exception is generated while a user is using it, the user will get the option to send an error report to Microsoft. You can register to receive these reports of your crashes at http://msdn.microsoft.com/isv/resources/wer/.


Using Profiling Tools

Debugging performance problems is one of the most difficult challenges for programmers. Fortunately, several tools exist to help you profile applications so you can see where problems lie. Although performance profiling and optimizing a program is a huge subject, it is generally based on a simple set of steps. I find that most performance optimizations follow these steps:

  1. Time the execution of your application to create a baseline. This is an important step because it will allow you to judge whether your changes really have enhanced performance. This means running your applications and generating some load on them, either by directly interacting with the program or preferably by using a script to automatically perform tasks that would commonly be performed by users. The advantage of using a script is that you can more easily run your test multiple times. The tool ntimer.exe is good for this.
  2. Use a profiler to create a profile of your application. This will allow you to look at how your application could be enhanced. It is important to perform this as a separate step from your baseline because profilers can seriously slow the execution of your application.
  3. Form a hypothesis about what is causing your code to run slowly. This is the most difficult part of any performance investigation and generally involves a detailed analysis of the profile you generated.
  4. Make changes to your code base, based on the conclusions you came to in the previous steps.
  5. Rerun the baseline you took of your application. This will allow you to see whether your hypothesis is correct and whether you have enhanced the performance of your application.
  6. If you have enhanced the performance of your code base, then you should commit the changes you made; otherwise, you should throw them away.

Typically, you'll repeat these steps until you are happy with the performance of your application. You'll now look at some tools that can help you profile your applications.

Ntimer

Although not actually a profiler, ntimer.exe is a nifty little tool that allows you to get the overall execution time for a program, which is useful for establishing a baseline for application performance. It's part of Windows 2003 Resource Kit Tools. Using ntimer.exe couldn't be simpler; just run ntimer followed by the name of the program you want to time and any command-line arguments you want to pass to it.

Perfmon and Performance Counters

Perfmon is a monitoring tool built into Windows, so it's readily available on every Windows machine. It allows you to examine performance counters that reveal information about almost every aspect of a machine. Select Control Panel image Administrative Tools to open it (see Figure 12-4). The three counters that are loaded by default, Pages/sec (the number of pages swapped from disk each second), Avg. Disk Queue (the amount of information in the queue to be read or written to the disk), and % Processor Time (the amount of time the processor is actually in use), give you a good idea of the overall health of the machine. If any of these values are high, then the machine will probably seem slow and unresponsive.

image

Figure 12-4. Perfmon, a tool for monitoring performance

If you want to examine other aspects of the machine's performance, you can add more counters by right-clicking the graph pane and choosing Add Counters. Since each piece of software installed on a machine can install its own counters, the number of counters varies from machine to machine, but a typical machine has at least 50 categories of counters (performance objects) and more than 100 counters. To help you navigate this maze of counters, Table 12-14 summarizes some of the most useful ones to the .NET programmer. It's important to remember when adding counters that most counters either can be the total for the machine or can be the total for a specific process; often it best to choose the one that is specific to the process you are trying to profile.

Table 12-14. Useful Performance Counters and Their Meanings

Performance Object Counter Description
Process % Processor Time This is the amount of processor time consumed by the process.
Process Page Faults/sec This is the number of page faults per second. If this number increases dramatically, this means the process does not have enough memory to operate effectively; you need to reduce your memory consumption.
Process Handle Count This represents the number of files the process has open. If this number increases throughout the lifetime of an application, you are probably leaking file handles. In other words, you forgot to close a file stream by calling its Dispose() method.
Process Private Bytes This is the number of bytes used by the process that are not shared with other processes. If this figure continues to rise throughout the lifetime of an application, this typically means you have a memory leak.
.NET CLR Exceptions # of Exceptions Thrown/sec This measures the amount of .NET exceptions being thrown. If this number is high, then your application will perform poorly since exceptions are relatively expensive in .NET.
.NET CLR Jit % Time in Jit This tells how much of the CLR's time is spent just-in-time compiling the application. If this figure is too high, then you might consider using ngen.exe when you deploy your application. This will precompile the application, removing the need for the JIT compiler.
.NET CLR Memory # Bytes in all Heaps This is the amount of memory consumed by managed objects in your program. If this figure grows continually, then you might have a memory leak.
.NET CLR Memory % Time in GC This is the amount of time your application spends in garbage collection (GC). If this number is too high, then you might need to consider changing the way you use memory; typically you will be looking to reduce the number of objects you create. However, do not panic if the application spends a seemingly large amount of time in GC; typically, most applications, managed or unmanaged, spend a lot of time managing memory. If this figure is greater than 20 percent, then there might be cause for concern. However, only if the figure is consistently higher than 50 percent should you put some effort into trying to bring this number down.
.NET CLR Memory Finalization Survivors This is the number of objects that were not garbage collected because they required finalization. If this counter is high, it means that you are forgetting to call the Dispose() method on objects that implement the IDisposable interface, which will add unnecessary overhead to your application.

Although the counters in Table 12-2 will serve as a useful starting point when investigating any .NET performance problem, it's probably best not to limit yourself to these counters. It is usually best to try to find the counter that most directly relates to your problem.


Caution Memory leaks can still occur in managed applications, typically when you still have references to objects that you are no longer using. For example, if your program places an object in a collection at the top level of a module and doesn't remove it when it has finished with it, then the memory associated with that object has effectively been leaked, since it's no longer in use and won't be reclaimed.


You can also use Perfmon to log performance counter values to a file. This can be useful when monitoring applications in production. Typically, you set up Perfmon to log counters you think will help you diagnose performance problems. Then if users complain about a sudden drop in application performance, you retrieve the logs to evaluate what caused the problem. Logging performance counters to a file is quite easy and intuitive; you just need to open the Performance Logs and Alerts node on the left side of Perfmon, then right-click the main pane, and finally choose New Log Settings, which allows you to choose the counters you want to log and when you want to log them. Again, for this logging to work correctly, you must start the Performance Logs and Alerts service, which is not started by default.


Note You can read performance counter values within your code and create your own performance counters using the class System.Diagnostics.PerformanceCounter. You can find more information about how to do this at http://strangelights.com/FSharp/Foundations/default.aspx/FSharpFoundations.PerfCounters.


NProf

NProf is a timing profiler. It measures the amount of time it takes to execute a method and displays this to the user as a percentage of the overall execution time. As a timing profiler, it is suitable for investigating problems where you believe a section of code is running too slowly. It is an open source utility available from http://www.sourceforge.net.

NProf, shown in Figure 12-5, is quite straightforward to use; it is the interpretation of the results that can be the tricky part. To start a profile, just select the executable you want to profile, and give its command-line arguments; alternatively, you can connect to an ASP.NET application. When you've chosen the application you want to profile, you can start profiling by pressing F5, and when the application finishes, NProf will list all the methods called during the application and statistics about them such as the number of times a method was called and the overall percentage of time spent in this method. There is also a window that allows you to see the callees of the method.

Once you've collected this information, you usually look at which methods take the highest overall percentage of execution time and consider how this can be reduced. Typically, you look at the callees to try to figure out whether any of them can be removed or replaced with calls to other methods that execute faster.

image

Figure 12-5. The NProf profiler GUI

It's also worth looking at the methods that have the highest number of calls. In this case, you typically look at the callers to try to figure out whether a method is being called when it's not necessary to call it.


Note At the time of this writing, two releases of NProf, 0.10 and 0.9.1, are available. Both are available from http://nprof.sourceforge.net. Release 0.10 runs considerably faster than 0.9.1, but the UI has been cut down to the bare minimum. I actually prefer the 0.9.1 version because I find it easier to use. I hope future releases will revert to the old UI while retaining the speed of the latest version.


CLR Profiler

Despite its name, the CLR Profiler is not a general-purpose profiler for the CLR. It is, in fact, a managed-memory profiler. If you see that your application has memory-related performance problems but have no idea what is causing them, then the CLR Profiler can help you get a better idea of what types of objects are taking up memory and when they get allocated.

The CLR Profiler can generate several different types of graph to help you visualize how memory is being used. It can generate an allocation graph that shows which methods created which object types, a histogram of all the allocated types, histograms of objects by ages and address, and timelines of object allocation.

Perhaps the most useful feature is the ability to generate a histogram of the types in use on the managed heap, as shown in Figure 12-6. This allows you to get a better idea of which types are consuming memory. With this knowledge, you can review the code and look for places where you can remove instances of types.

image

Figure 12-6. A histogram generated by the CLR Profiler

CLR Profiler also has a command-line mode where it logs its results to a file. This is useful because you can use it as part of an automated testing process to check that each build doesn't have any regression errors.


Note This tool is freely available for download from http://www.microsoft.com/downloads/details.aspx?familyid=a362781c-3870-43be-8926-862b40aa0cd0&displaylang=en.


Summary

In this chapter, you surveyed a number of development tools that help make the lives of the F# users easier. Some tools, such as fsi.exe (F# interactive), are very flexible, and you'll probably use them every time you code in F#; others you'll use less frequently to track down tricky bugs. In the next chapter, you'll look at compatibility and advanced interoperation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset