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.
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.
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. |
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.
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. |
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. |
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
.
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.
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
.
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.
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. |
The --progress
flag shows the progress of the compiler to let you know a little about what is going on inside the compiler.
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 . |
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.
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
Note The Ctrl+C combination cannot be used to quit F# interactive because Ctrl+C is used to abort long-running computations.
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. |
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. |
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__);;
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.
Figure 12-1. The NUnit GUI
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.
Figure 12-2. Reflector, a class browser
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 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 Windows 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.
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/
.
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:
ntimer.exe
is good for this.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.
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 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 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.
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
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 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.
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.
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.
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
.
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.