The F# Tool Suite and .NET
Programming Tools
T
his chapter will be a little different from most of the chapters in this book; instead of focus-
ing 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
\bin directory. You can find the F# compiler, at the
time of writing, in
c:\Program Files\FSharp<version>\bin 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,
y
ou
’
ll examine various ways you can use
fsi.exe mor
e
effectiv
ely.
Using Useful fsc.exe Command-Line Switches
You can view the basic F# command-line options using the -help switch; I describe them in
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 pr
ints 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 wr
ites the doc comments for the assembly to the giv
en file.
D
oc
comments
are a special type of comment and are intended to create
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 infor-
mation 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 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\n" (sqrt(!vBv / !vv))
main()
I then compiled this into a number of different executables, differing only by optimiza-
tion 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 Command Line Kernel User Total Percentage
Level 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
-O1
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
--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 pr
oduces 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 distr
ibuted
with the .NET SDK. However, this functionality is not used very often.
Signing and Versioning Switches
Assemblies must
be cr
yptographically signed and have a version number before they can be
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, keep-
ing 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
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.
, descr
ibed in Chapter 6, into an
XML file. Tools such as NDoc or Microsoft’s Sandcastle can then turn these into different docu-
mentation for
mats. Although F# ultimately produces .NET code, its type system is, practically
speaking, mor
e expr
essiv
e than the .NET type system and uses .NET constr
ucts in po
w
erful
ways; ther
efor
e
, under some cir
cumstances
, these tools do not always do a good job pr
oducing
documentation for F# assemblies
. That is why the compiler provides a set of switches to pro-
duce HTML documents dir
ectly
.
T
able 12-8 summar
izes the HTML documentation switches.
CHAPTER 12
■
THE F# TOOL SUITE AND .NET PROGRAMMING TOOLS
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
impor
tant because there are variations in the types and methods that exist in the different ver-
sions. 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
summar
izes the CLI v
ersion switches.
T
able 12-9.
CLI V
ersion F# Compiler S
witches
Switch Description
--cli-version <string> This flag controls the version number of the CLI that is used. It can
take the v
alues
1.0, 1.1, and 2.0 and custom build tags such as
v2.0.x86chk.
Y
ou may need to use the flag
--clr-root to dir
ect 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
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 featur
es
, such as automatically timing program execution and quitting the com-
piler, 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 allo
ws an assembly to be r
efer
enced b
y
fsi.exe, meaning that
programs created within the console can use their types and
v
alues
. I
t 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 r
eferenced assemblies. If a filename, or a relative
file path, is used when using the
#r command, then this list of