Introduction
In this chapter, we discuss two pragmatic topics: how to
package your code for consumption, and how to consume code in other libraries.
The intra- and inter-language capabilities of .NET languages offer a certain
appeal for many developers, enabling them to apply the most appropriate
language to the task at hand without sacrificing overall system compatibility.
Organizing Your F# Code
Namespaces
A namespace is the highest level of code
organization. A namespace enables us to organize our code into logically
related areas of functionality by grouping program elements under a common naming
umbrella. The namespace becomes an intrinsic part of the name of the elements
within it. Namespaces are used primarily to structure our code hierarchically and
to avoid namec conflicts. To specify a namespace, you use the following syntax:
namespace [parent-namespaces.]identifier
Let’s suppose we’re creating an enterprise application, and
we decide to divide the functionality into several tiers – a UI tier, a
business tier, and a database tier. To keep code logically and semantically
separated, we could define namespaces that segragate this functionality, e.g.:
namespace UI
namespace Business
namespace Database
We can include “sub” or “child” namespaces under a “parent”,
to further refine and separate out our code’s functionality, using dot notation
e.g.,:
namespace Database.SQLServer
namespace Database.SQLServer.Client
namespace Database.MySQL
Namespaces cannot directly contain values and functions.
Instead, they can contain modules, types and do bindings. Namespaces
can be declared explicitly (as shown above) with the namespace
keyword, or implicitly via declaring modules.
Namespaces can span multiple files in a single project or
compilation unit. The term namespace fragment describes the part of a
namespace that is included in one source file. Namespaces can also span
multiple assemblies. For example, the System namespace
includes the whole .NET Framework, which spans many assemblies and contains
many nested (“child”) namespaces.
Modules
Another grouping mechanism that F# provides is the module.
An F# module is a simple mechanism for grouping types, values, function values,
and the code in do bindings. F# compiles a module into a common
language runtime (CLR) class that has only static members. We
declare modules using the following syntax:
//
Top-level module declaration
module
[accessibility-modifier] [qualified-namespace.]module-name
declarations
//
Local module declaration
module
[accessibility-modifier] module-name =
declarations
Modules impact identifier scope. Inside a module,
identifiers are all visible to one another, but are not visible externally. To
reference identifiers defined in external modules , your code must full qualify
the identifier names, e.g., MyModule.myIdentifier or use the open
keyword as discussed in Chapter 2.
By default, each module is contained in a single source
file. The entire contents of the source file make up the module, and if the
module isn’t explicitly named, it gets the name of its source file, with the
first letter capitalized, e.g., a file named fileIO.fs would produce
a module called FileIO.
Top Level Modules
To explicitly name a module, we use the following syntax at
the top of the F# source file:
module [accessibility-modifier]
[qualified-namespace.]module-name
declarations
When we explicitly define a module in this way, we are
defining a top-level module. Providing a top-level module is optional,
and if we choose to do so, the module name must appear as the first declaration
in the source file. A top-level module lends its name to the entire source
file, which means that all elements in the source file are intrinsically
compiled into the static class produced by the compiler. Note that you do not
need to indent structures within a top-level module to have them be included in
the module. In the following example, we show an F# file (program.fs)
that contains a top-level module called Utils:
module
Utils
let
ReverseString s = ()
let IsPrime = ()
The way this is written, this code defines two functions that
are named Utils.ReverseString and Utils.IsPrime. If we
were to omit the top-level module declaration, these functions would have been
part of the default module Program, which would have been generated
by F# based on the name of the file, program.fs, with the
first letter capitalized.
Local Modules
In contrast to top-level modules, F# enables us to define local
modules, which are used to name and organize small sections of a larger
file. To define a local module, you use the following syntax:
module
[accessibility-modifier] module-name =
declarations
Note the equal sign (=) at the end of the
module name, as well as the indented module declarations.
Similar to namespaces, module names can contain dots,
allowing us to organize and structure the name into a hierarchy of meaningful parts.
In the following example, we have a file (program.fs) that will
provide for us the default top-level module name Program. Within program.fs,
we define two local modules that organize and scope the contained constructs:
// Default module generated is Program
module
Employee =
let mutable
Name = ""
let CalcPay emp = ()
module
Company =
let GetEmployees = ()
let Hire e = ()
This code defines two local modules, Employee
and Company.
Emplopyee and Company define two separate naming scopes. To
access CalcPay from outside this file, for example, clients would
need to access Employee.CalcPay, as this is it’s full name.
Continuing with this example, here is a snippet of code used to access CalcPay:
open Program
Employee.CalcPay 123
Because Employee is a module in its own right,
we can also code this call as follows:
open
Program.Employee
CalcPay 123
Nested Modules
F# also enables is to nest modules within one another. Nested
modules must appear indented under their parents and their names cannot
include dots. In the following example, we define three local modules with
increasing degrees of nesting. Through this example, we also demonstrate how
modules introduce scope:
// Default module generated is Program
module
GrandPa =
let Name = "Philip"
module Dad =
let Name = "Adam"
module Son =
let Name = "Tom"
The following code snippet makes use of these nested
modules:
open
Program.GrandPa
let
printNames =
printfn "%s" Name
printfn "%s" Dad.Name
printfn "%s"
Dad.Son.Name
As you can see here, the same identifier, Name,
can be used in each module without any problems, due to the fact that the full
name, e.g., Dad.Name, is unique.
Module Aliases
It is sometimes convenient to provide short or distinct
names to modules. This can help with potential name clashes and/or save a bit
of typing. To access a module and give it an alias, you use the following
syntax:
module alias = existing-module-name
Let’s look as an example. Here, we define a long module
name, and define a function inside the module:
module
MyVery.Long.Module.Name
let
MyFunc() =
printfn "MyFunc"
We can access MyFunc in another file
using a module alias, like this:
module
MyModule = MyVery.Long.Module.Name
MyModule.MyFunc()
Note that some older texts say that you can access
namespaces using this syntax. As of this writing, this is deprecated
functionality. You are now only able to alias modules.
Order Matters
F# requires that types, values and functions are defined
before they can be used. This means that if you require a particular construct
in a definition, this construct must be already defined. Let’s consider an example.
Suppose we have two modules, Company and Employee.
As part of an Employee’s definition, definition we want to record
information about the Company. This means that the Employee
module will need information from the Company module.
If we build our F# application using the command-line
compiler, fsc, we need to pass the Company.fs file to the
compiler followed by Employee.fs, e.g., fsc Company.fs Employee.fs –o
Corp.exe. This will compile fine; however, if we switch the order
of the files, we’ll get a compilation error to the effect that the module Company
is not defined.
This ordering “rule” is reflected in Visual Studio as well.
In the Solution Explorer window, the order in which the files are listed is the
order in which they are compiled, i.e., that they are passed to the F#
compiler. To change the ordering of the files, you can right-click on a file
name (in Solution Explorer) and select Move Up or Move Down,
among other options.
You must be asking yourself what to do if we have classes
that are circularly dependent. Recall from our discussion of types that in
order to define types that reference one another, we must use a conjoined
definition using the and keyword, as in the following example where Actor
depends on Film and Film on Actor
:
type
Actor(f: Film) =
do ()
and
Film(a: Actor) =
do ()
What this means in terms of modules is this: classes that are
circularly dependent must be defined in the same module using the and
keyword.
Execution
Program execution begins with the last module passed
to the compiler. All of the elements in this file/module execute, starting from
the top of the file and working toward the bottom, as you’d expect. If your
program consists of more than one module, none of the other modules’ top-level
statements will execute until one of their values is accessed.
Let’s look at an example. In this example, we have three
files: Module1.fs, Module2.fs and Program.fs.
These are listed in order below (#light is defined in
each):
// Module1.fs
printfn
"I am Module1"
let sayHello() = printfn "Module1
saying hello!"
// Module2.fs
printfn
"I am Module2"
let sayHello() = printfn "Module2
saying hello!"
// Program.fs -> module Program implied
printfn
"I am the main program"
Assume these are compiled in the order shown, making Program.fs
the last module passed to the compiler. As a result, when the program begins
executing, all of Program.cs’s top-level will run. This also
means that, as written, none of the statements in Module1.fs nor in Module2.fs
will execute. The output of this program, as expected, is I am the
main program.
If we augment the Program.fs file to open
and use these other modules, the top-level statements in the modules may or may
not execute. Merely opening the module is not sufficient to stimulate
execution, nor is calling a function. To ensure that a module’s top-level
statements execute, a client must access one of the values in the module via a running
function or expression. In the following, updated code, Program.fs
opens both modules, calls a function in each, and accesses a value in only one
of them:
// Module1.fs
printfn
"I am Module1"
let public mod1Val = 100
let
sayHello() = printfn "Module1 saying
hello!"
// Module2.fs
printfn
"I am Module2"
let public modVal2 = 200
let sayHello() = printfn "Module2
saying hello!"
// Program.fs -> module Program implied
open
Module1
open
Module2
printfn
"I am the main program"
Module1.sayHello()
let m2 =
Module2.modVal2
Module2.sayHello()
The output of running the application, i.e., running Program.fs
is as follows:
I am the main program
Module1 saying hello!
I am Module2
Module2 saying hello!
Press any key to continue . . .
As shown, Module1’s top-level
statement is not executed, since we never accessed its value (mod1Val).
We merely called Module1’s sayHello function,
which executed correctly. In contrast, we did access Module2’s
value (modVal2), which stimulated F# executing its top-level
statements. We also called a function in Module2, which executes,
as expected.
Functions
F# also supports encapsulation and scoping via local
functions, a.k.a., inner functions (as discussed in Chapter 8). Inner
functions are those that are defined within other functions. In functional
programming, we implement inner functions to hide complexity from clients,
e.g., the client does not need to know a function is recursive, and to break up
complex functions into manageable parts. From the perspective of code
organization, it’s a technique that you should be aware of. Here is an example
of an inner function from the F# Wikibooks:
let
sumOfDivisors n =
let rec loop
current max acc =
if current > max then
acc
else if n %
current = 0 then loop (current + 1) max (acc +
current)
else loop (current + 1) max acc
let start = 2
let max = n / 2 (*
largest factor, apart from n, cannot be > n / 2 *)
let minSum = 1 + n (*
1 and n are already factors of n *)
loop start max minSum
printfn
"%d" (sumOfDivisors 10)
(* prints 18, because the sum of 10's divisors is 1 + 2 +
5 + 10 = 18 *)
Signature Files
A signature file is a mechanism for controlling the
accessibility of a module’s types, values, functions, etc. You can create a
signature file per F# source file. Each signature file shares the name of the
source file with which it’s associated, expect that it has an .fsi
extension.
A signature file describes the namespaces, modules, types,
and members of its associated source file. We use signature files to control which
elements of the source file are exposed to the outside world, and which remain private
to the file itself. As a rule of thumb, constructs not listed in the signature
file are considered to be private to the file itself. If no signature file is
found in the project (or via the command line), F# employs default
accessibility as specified via public, internal
and private.
The format of a signature file is rather simple. For each
type, method, etc. that we want to expose, we use the signature for the
element, which acts as a complete specification of the functionality being exposed.
The syntax for a type signature is the same as that used in abstract method
declarations ala interfaces and abstract classes. Signature files work with
Intellisense, too. To expose a value or function using a signature file, we use
the keyword val. To expose a type, we use the keyword type
Signature files can include attributes that further refine
the information exposed to clients. The two attributes used in signature files
are [<Sealed>]
and [<Interface>],
which describe types that cannot be extended (Sealed) and types that
are interfaces (Interface) respectively.
Generally, you auto-generate signature files using the
command-line compiler option –-sig and then manually
remove the entries that you want to keep private.
The MSDN
documentation describes several rules regarding signature files, reproduced
here for your convenience:
There are several rules for type signatures:
- Type abbreviations cannot be hidden by signatures.
- Records and discriminated unions must expose either all
or none of their fields and constructors, and the order in the signature
must match the order in the implementation file. Classes can reveal some,
all, or none of their fields and methods in the signature.
- Classes and structures that have constructors must
expose the declarations of their base classes (the inherits declaration).
Also, classes and structures that have constructors must expose all of
their abstract methods and interface declarations.
- Interface types must reveal all their methods and
interfaces.
The rules for value signatures are as follows:
- Modifiers for accessibility (public, internal, and so
on) and the inline and mutable modifiers in the signature must match those
in the implementation. Editorial comment: I found that I could
override accessibility via the signature file. For example, in my module
(below), the SuperCalc type class is marked public,
but I could cause it to be marked internal by the
compiler via the signature file. It appears as though the signature file
can override the accessibility modifiers in the code.
- The number of generic type parameters (either
implicitly inferred or explicitly declared) must match, and the types and
type constraints in generic type parameters must match.
- If the Literal attribute is used, it must appear in
both the signature and the implementation, and the same literal value must
be used for both.
- The pattern of parameters (also known as the arity) of
signatures and implementations must be consistent.
As of this writing, Visual Studio 2008 using F# CTP 1.9.6.16
does not produce valid signature files. If I compile my application using the Visual
Studio-generated FSI files, the compiler reports problems with the files - issues
with val and incomplete structures. That said, the command-line
compiler seems to produce valid signature files just fine. So, for now, to
generate signature files, I’d suggest using the command line. Let’s look at an
example. Our example application consists of two files: Module1.fs
and Program.fs:
// Module1.fs => Module1 module name
type
Calc() =
member this.Add x y = x + y
type public SuperCalc() =
member this.Mul x y = x * y
// Program.fs
open
Module1
let c = new Calc()
let sum
= c.Add 1 2
To produce a signature file for Module1.fs, we execute
the following command-line command:
fsc -–sig:Module1.fsi Module1.fs
The compiler will produce a file called Module1.fsi. Here’s
what it looks like:
#light
module
Module1
type
Calc =
class
new : unit ->
Calc
member Add : x:int ->
y:int -> int
end
type
SuperCalc =
class
new : unit ->
SuperCalc
member Mul : x:int ->
y:int -> int
end
Now, I’d like to hide SuperCalc from the
outside world. To do this via the signature file, I open Module1.fsi
in the Visual Studio (or any) editor and remove the SuperCalc
entry. The updated signature file looks like this now:
#light
module
Module1
type Calc
=
class
new : unit ->
Calc
member Add : x:int ->
y:int -> int
end
Now I can compile the program as follows:
fsc Module1.fsi Module1.fs Program.fs
Signature files are optional.If present, they must precede their
implementation files in compilation order on the compiler command line. If
you’re using Visual Studio to build your projects, this means ensuring that
each signature file comes before it’s associated implementation file in the
Solution Explorer.
Executing the above command produces Program.exe,
which contains the correct modules with the specified accessibility. The Calc
class is public, while the SuperCalc class is
marked as internal. You can use .NET Reflector to verify
this.
My recommendation is that you rely on in-situ accessibility
modifiers, i.e., modifiers specified in the F# code itself, rather than rely on
specifications in the sig file. To my mind, this is simpler, as you only need
to specify accessibility once and only once. It also reduces the number of
files you need to maintain. That said, many F# programmers like to use
signature files for two reasons:
·
They can be used to make entities public for the duration
of the file (because signature files are per implementation file), but then private
to the subsequent files of the project.
·
They can provide a documented, public interface to consumers,
which can be a clean, condensed and easy-to-read format vs. having consumers
wade through lots of code.
Deploying Your F# Code
You’ve built something useful in F#. Now you’re ready to
deploy it. A great deal has been written about how to build, sign, version and
deploy .NET applications. I am not going to rehash that material here.
Creating F#-based assemblies is no different from creating
assemblies in other .NET languages. Using F#, you create .NET EXEs and DLLs. The F#
compiler supports a host of options , including those for static linking
libraries, defining debug directives, etc. The F# EXEs and DLLs that you create
are linked to create one or more .NET assemblies. Please consult the MSDN
library regarding the options you have with respect to deploying .NET
assemblies. Don Syme’s Expert F# also covers deployment in detail.
The AutoOpenAttribute
The AutoOpen attribute can save your clients a bit
of work. If you want to set up your own assembly or module so that the F#
runtime automatically makes available its contents, you can apply the AutoOpen
attribute at the assembly or module level.
Here is what the MSDN
documentation has to say:
You can apply the AutoOpen
attribute to an assembly if you want to automatically open a namespace or
module when the assembly is referenced. You can also apply the AutoOpen
attribute to a module to automatically open that module when the parent module
or namespace is opened.
Interfacing with the .NET Libraries
F# Server, C# Client
Once you’ve developed, tested, debugged and packaged your F#
code, you may need to integrate it into a larger system. It’s not far-fetched
to assume that some of the libraries in this larger system are written in C#. In
this section, we’ll demonstrate how to create an F# library (DLL) and access if
from C#. To get started, we’ll create a new Visual Studio solution called
Fusion. This solution will consist of an F# library project and a C# Console
project.
Simple Interactions
In our solution, the F# library defines project a namespace FSharpCalc
and a top-level module CalcEngine from which it exposes a type Calculator
and a function GCD. Here are the definitions:
namespace
FSharpCalc
module
CalcEngine
// Calculator type
type
Calculator() =
member this.Add x y = x + y
member this.Sub x y = x - y
member this.Mul x y = x * y
member this.Div x y = x / y
// Greatest Common Divisor function
let rec GCD x y =
if y = 0 then x
else GCD y (x % y)
When we compile this code, we produce a DLL called FSharpCalc.dll.
Note that the namespace is important
in that it enables the C# client to access the contents of the library via its standard
using
directive. If you omit the namespace in the F# code, the default module
machinery kicks into gear, which causes the module to be named after the source
file. My recommendation is to use namespaces whenever you
can. Using namespaces has several benefits including making the code clear and
transparent, helping to avoid name collissions, and simplifying consumption.
In our sample solution, the client is a simple C# Console
application that exercices the F# library’s functionality. In order to access
library, the C# project needs a reference to the FSharpCalc.dll. Once it
has a refernece, the C# client accesses the library’s namespace via a standard using
directive. From there on in, the client can create Calculator objects,
call their methods, call the global GCD function, etc. Here
is the source code for the C# Console application.
using
System;
using
FSharpCalc;
namespace
CSharpClient
{
class Program
{
static void Main(string[] args)
{
var c = new CalcEngine.Calculator();
int s = c.Add(1, 2);
int p = c.Mul(3, 5);
int g = FSharpCalc.CalcEngine.GCD(8,
20);
Console.WriteLine("Sum={0},Product={1},GCD={2}",
s, p, g);
Console.ReadLine();
}
}
}
While simple, this example demonstrates the core ideas of
calling F# code from C#. To be pragmatic, let’s add some functions to the CalcEngine
module that deal with more sophisticated data types such as arrays, lists and
events to see how to deal with them properly. Along these lines, we have added
the following function to the F# library:
// Sum a list using fold (we could use sum as well)
let SumList
(lst) =
let accumulate acc x = acc + x
let sum = Array.fold accumulate 0 lst
sum
This function accepts a list of values and uses Array.fold
to compute the arithmetic sum. Since we’re using the Array
type to conduct the fold operation, F#’s type inference system will
expose an Array type to client, i.e., C# will see the argument lst
as an array type. This means that the client is expected to pass in an array
argument. This is exactly what the C# client does, as shown here:
using
System;
using
FSharpCalc;
namespace
CSharpClient
{
class Program
{
static void Main(string[] args)
{
int[] myIntArray = new
int[5] { 1, 2, 3, 4, 5 };
int sum = CalcEngine.SumList(myIntArray);
Console.WriteLine("sum
of 1-5 = {0}", sum);
Console.ReadLine();
}
}
}
To make the F# code more generic, we can replace the Array
implementation and expose the collection as an IEnumerable by using
the Seq
type.
// Sum a list using fold
let
SumList (lst : #seq<'a>) =
let accumulate acc x = acc + x
let sum = Seq.fold accumulate 0 lst
sum
Of course, the type argument the client supplies must
implement a plus (+) operator for us to avoid a run-time error (x = acc +
x could fail). Let’s update the C# client to an IEnumerable:
using
System;
using
System.Collections.Generic;
using
FSharpCalc;
namespace
CSharpClient
{
class Program
{
static void Main(string[] args)
{
// Pass in a List<T>
var myList = new
List<int>(new[] {10, 20, 30, 40});
var sum = CalcEngine.SumList(myList);
Console.WriteLine("sum
of 10-40 by tens = {0}", sum);
Console.ReadLine();
}
}
}
We can also calls functions that accept arguments of type unit
and return unit as well (unit -> unit). For
example, we can add the following code to FSharpLib:
// Function that takes nothing and return nothing
let
printHello() =
printfn "Hello from
FSharpCalc.CalcEngine!"
and call it from C# like this:
CalcEngine.printHello();
Note that when we define the function in F#, we use the
empty ( ). This ensures that C# sees the function as a function
type and not a property.
Events
Because F# is a fully-supported .NET language, almost every
construct you define in F# can be consumed by C# and other .NET languages. The
tricky part is getting the F# types and the C# types to align. Let’s look at an
example that can be somewhat vexing – events.
In order to define events in F# that can be consumed by C#,
you need to use delegates and the [<CLIEvent>]
attribute. In the following example, we reconstruct the FSharpLib
to define an event that’s fired whenever the Add method is called:
// Calc Library
namespace
FSharpCalc
module
CalcEngine
open
System
// Event arg
type
AddEventArgs(msg: string) =
inherit EventArgs()
member this.Message = msg
// Event delegate
type
AddEventDelegate = delegate of obj * AddEventArgs ->
unit
// Calculator that can now raise an event
type
Calculator() =
// Create Event<handler, args>:
let ev = new
Event<AddEventDelegate, AddEventArgs>()
member this.Add (x: int) (y: int) =
let sum = x + y
// Fire event
ev.Trigger(this, new
AddEventArgs(String.Format("Added {0} and
{1}", x, y)))
sum
// Magic attribute that exposes the event to other
.NET languages
[<CLIEvent>]
member this.OnAdd = ev.Publish
In this example, when we define the AddEventDelegate
via the delegate keyword, the F# compiler generates a
MulticastDelegate. The MulticastDelegate is
general purpose, and is generally sufficient to implement cross-language events;
however, if you want, you can specify the specific delegate type that F#
should use via the DeletegateEvent (vs. using the plain Event
as we’ve done above). In the following example, taken from a hubFS post by Tomas Petricek,
you can see how these classes are used:
// Declare class for testing, which can be used from C#
type
Test() =
// Declare event with 'EventHandler' delegate
let evt1 = new
DelegateEvent<System.EventHandler>()
// Declare event that'll use standard F# generic
delegate type
let evt2 = new
Event<System.EventArgs>()
// Expose the first event as C# event
[<CLIEvent>]
member x.Event1 = evt1.Publish
member x.Foo1() = evt1.Trigger([| box x; box
System.EventArgs.Empty |])
// trigger it
// Expose the second event as C#-compatible event
[<CLIEvent>]
member
x.Event2 = evt2.Publish
member x.Foo2() = evt2.Trigger(System.EventArgs.Empty) // trigger it!
High-Order Functions
Another interesting situation that arises when building F#
components for C# consumption revolves around high-order functions. When you design
an F# function that accepts function arguments, or returns a function, how do
you call this from C#?
We answer this question by way of an example. First, we
define an F# function, filterStringsFromList that filters a list of
strings based on a filtering function. The caller will pass filterStringsFromList
the filtering function to use. This example was adapted from Robert Pickering’s
Foundations of F# book:
let
filterStringsFromList f (l : List<string>) =
new List<string>(l |> Seq. filter
f)
As you can see here, we’re using standard F# high-order
function semantics. The first parameter to filterStringsFromList
is a standard F# function. The second parameter is a list of strings to filter.
Under the covers, F# exposes high-order function parameters via built-in type
called FastFunc. This means that .NET client applications that
want to call this function need to work with FastFunc types. To gain
access to the FastFunc type, we include a reference to the FSharp.Core.dll
in our C# project. The FSharp.Core.dll includes a helper type, FuncConvert
that C# applications can use to wrap local functions, making them compatible
with F#’s FastFunc type.
In the following C# client example, we use the types defined
in FSharp.Core.dll
to call the HighOrder.filterStringsFromList function
defined in our F# library:
static void PrintJNames()
{
var
names = new List<string>(
new string[] { "Jessica" , "Kimberly",
"Jill" , "Melissa"
});
Converter<string,
bool> pred = s => s.StartsWith("J");
FastFunc<string,
bool> ff = FuncConvert.ToFastFunc<string, bool>(pred);
IEnumerable<string>
jnames =
HighOrder.filterStringsFromList(ff,
names);
foreach (string
name in jnames) {
Console.WriteLine(name);
}
}
A few notes about the code sample are in order. The Converter
class is a .NET class used to convert one type to another. Here, we are using
it to wrap a lambda function and create a delegate, which is required by FuncConvert.ToFastFunc.
We then use FuncConvert.ToFastFunc to convert this
delegate into a FastFunc so that we can pass it into the F# filterStringsFromList
function.
While this works perfectly well, I think it’s fair to say
that working with FastFunc objects is somewhat awkward. Luckily,
we have a simpler solution. If we begin in F# by defining our filterStringsFromList
function using a delegate vs. a raw function, this makes calling the function
from C# simpler. Let’s see what it looks like:
namespace
FSharpLib
open
System
open
System.Collections.Generic
module
HighOrder =
// New version that uses delegate vs. raw F# function
for filter function
let filterStringsFromList (del :
Predicate<string>) (l : List<string>) =
let f x = del.Invoke(x)
new List<string>(l
|> Seq. filter f)
The new version of filterStringFromlist is
only slightly different from the original. The first argument, the Predicate,
is now a .NET class that inherits from the MulticastDelegate class.
We can use any MulticastDelegate-derivate to expose our F#
function arguments to the .NET-world-at-large. Often, we use the delegate types
from the System namespace’s Func<> family of
classes, e.g., Func<T, TResult>. Please consult the MSDN documentation
for details on Func<>
delegates.
Now that F# is exposing its function argument like this, we
can bypass FastFuncs and instead use C#’s lambda functions
directly. We demonstrate this in the following example:
using
System;
using
System.Collections.Generic;
using
FSharpLib;
namespace
CSClient {
class Program {
static void
Main(string[] args) {
var names = new
List<string>(
new string[] { "Jessica" , "Kimberly",
"Jill" , "Melissa"
});
var jnames =
HighOrder.filterStringFromList(s => s.StartsWith("J"), names) ;
foreach (string
name in jnames) { Console.WriteLine(name);
}
}
}
}
Clearly, this is much cleaner and more intuitive than the
original code. The moral of this story is: if you’re writing high-order
functions that you intend to be callable by C# and other .NET clients, expose
your arguments via delegates.
C# Server, F# Client
In contrast to accessing F# from C#, it’s more likely that
your F# code will leverage libraries already written in C# and other .NET languages.
This section deals with this scenario.
Simple Interactions
You can use F# to consume libraries written in other .NET
languages. We do this all the time when using classes from the .NET Base Class
Library (BCL). In the following example, we build a C# library that exposes a
class for downloading Web pages. We then consume this library from an F#
Console application. Here is the code for the C# library:
using
System.Net;
using
System.Text;
namespace
CSLib
{
public class HttpHelper
{
public string
GetWebPage(string url)
{
var req = new WebClient();
var payload = req.DownloadData(url);
return new ASCIIEncoding().GetString(payload);
}
}
}
Consuming this library from F# is straight forward. We
simply add a reference to the C# library in our F# project, open
the exposed namespace, and make calls as usual. Here is the F# Console
application:
#light
open
CSLib
let hh =
new HttpHelper()
let
yahoo = hh.GetWebPage("http://www.yahoo.com")
printfn "%s"
(yahoo.ToString())
Lists and Collections
Lists and collections are fundamental to programming. With
the inclusion of generics (and the upcoming contravariance and covariance
support in C# 4.0), the majority of developers will probably use the
generic-based collections defined in System.Collections.Generic.
Let’s write a C# class that uses these data structures, and then examine how to
access them from F#.
using
System.Collections.Generic;
namespace
CServer
{
public class MyType
{
public List<string> GetNames() {
return new List<string>(
new string[] { "Jess" , "Chee",
"MissyP" });
}
public int
CountThese<T>(IEnumerable<T> e)
{
int count = 0;
foreach (object
o in e) {
++count;
}
return count;
}
}
}
We can use these (very contrived) methods from F# as
follows:
open
System.IO
open
CServer
let o = new MyType()
let
names = o.GetNames()
names
|> Seq.iter(fun name -> printfn "%s"
name)
let c =
o.CountThese (seq { 1..10 })
printfn
"count = %d" c
As you can see, it’s straight-forward to access C# lists and enumerables from
an F# client.
ref and out
By default, in .NET, value types are passed by value. This
means that a copy of the value is passed into a function or method. In
contrast, reference types are passed via their memory addresses, meaning that a
change to the argument is, in reality, a change to the original. To override
this default argument-passing behavior, C# uses two keywords, ref
and out.
Both keywords instruct the C# compiler to pass arguments by reference. The only
difference between the two keywords is that out arguments must be
initialized (set to a value) before the called function exits. ref
parameters are not subject to this restriction.
From F#, when we call a function or methods that uses ref
and/or out parameters, we need to use reference cells to ensure
changes in the values are reflected back to us. In the following example, we
create a simple C# class with methods that do not use ref
or out,
use ref,
and use out:
namespace
CServer
{
public class MyType
{
public void
foo(int i) {
++i;
}
public void
bar(ref int i)
{
++i;
}
public void
quux(out int i)
{
i = 999;
}
}
}
We can see the effect of calling these methods from F# using
the following F# Console application:
#light
open
System
open
CServer
let x = new MyType()
let a,
b, c = 123, 456, 789
Console.WriteLine("before: {0} {1} {2}", a, b, c);
x.foo
a
let b' =
ref b
x.bar
b'
let c' =
ref c
x.quux
c'
Console.WriteLine("after : {0} {1} {2}", a, !b', !c');
Console.ReadLine() |> ignore
As you can see, to call methods that accept ref
and out
arguments , we use F# reference cells. Since we are not modifying the core
value (we are modifying the reference cell), we do not need to make the value mutable.
Recall that in order to dereference these cells, we use the bang (!)
operator. Note that in the case of calling method foo, we do not use
reference cells, since the argument is not decorated with ref
or out.
Of course, passing reference types, i.e., object-based
types, is done without argument decoration. We see this in the following
example:
namespace
CServer
{
public class MyType
{
public int
MyNum = 123;
public void
foo(int i) {
++i;
}
public void
bar(ref int i)
{
++i;
}
public void
quux(out int i)
{
i = 999;
}
public static void ModMyType(MyType
o) {
o.MyNum = 12345;
}
}
}
Given this C# server code, we can it from F# with standard
object (reference) semantics, as shown here:
open
System
open
CServer
let x = new MyType()
printfn
"before: x.MyNum = %d" x.MyNum
MyType.ModMyType(x)
printfn
"after : x.MyNum = %d" x.MyNum
Console.ReadLine()
|> ignore
Cleaning up resources
In F# programming, you will probably call into the .NET
libraries and leverage classes from the BCL. Many of the classes in the BCL
manage finite resources, e.g., file handles, graphics contexts, etc. allocated
by the underlying operating system. In order to avoid memory leaks, etc., you need
to deallocate these resources when you’re through using them. The standard
protocol for doing this in .NET is to use the IDispose
interface, call the Dispose method for the given resource object. To
make calling Dispose natural and painless, F# provides two
keywords: use and using.
The use keyword takes the following form:
use value = expression
It provides the same functionality as a let
binding with the added feature of instructing the F# compiler to call Dispose
on the value when the value goes out of scope. The following example shows how
to leverage the use keyword to automatically close a file:
open
System.IO
let
writeToFile filename (str: string) =
use theFile = File.CreateText(filename)
theFile.WriteLine("{0}", str)
// F# calls theFile.Dispose() automatically here
writeToFile @"c:\temp\poem.txt"
"Mary had a little lamb..."
The using
function has the following form:
using (expression) function-or-lambda
In a using expression, expression
creates the object that must be disposed. The result of the expression
(the object that must be disposed) becomes an argument to the subsequent
function or lambda expression. The following example demonstrates the using
expression with a lambda. Of course, you could replace the lambda with a
function call that takes a single argument of the type created:
open
System.IO
let
writeToFile filename (str: string) =
using (File.CreateText(filename)) (fun theFile ->
theFile.WriteLine("{0}", str)
)
writeToFile @"c:\temp\song.txt"
"You are my sunshine, my only sunshine..."
The using function and the use
binding are nearly equivalent ways to accomplish the same thing; however, the using
keyword provides more control over when Dispose is called. When
you leverage using, Dispose is called at
the end of the function or lambda expression; however, when you utilize the use
keyword, Dispose is called at the end of the containing code block,
when the use-d object goes out of scope.
COM Server, F# Client
We can use F# to call COM servers. The good news here is
that accessing COM via F# is pretty much the same as accessing it via C# and
other .NET languages. The beauty of COM is that it is a language-agnostic
interface, resulting in uniform client access.
Because .NET has been around for a while, and has matured,
you may find a managed wrapper already available for the COM components you’d
like to access, e.g., Microsoft Office 2007. In cases where a managed wrapper
does not exist, you can access the COM objects directly.
To access COM objects directly from F#, run the type-library
import tool (TLBIMP)
on the executable (DLL or EXE) that contains the definitions of the COM
objects. TLBIMP will create an OO wrapper assembly around the COM objects
contained in the executable. The resulting assembly can be used like any other
assembly – you add a reference to the assembly in your Visual Studio project or
use the-r switch on the F# compiler. Once loaded, the assembly
provides convenient access to the underlying COM components.
In the following example, based on one from Pickering’s
book, we run TLBIMP on the Speech API DLL executable to generate a wrapper
around the COM object model. On my machine, the Speech API is found here: C:\Windows\System32\Speech\Common.
Now that we have a wrapper DLL, we can use Visual Studio to
add a reference to it. Once we’ve done that, we can use the classes exposed by
the wrapper to access the underlying COM functionality. In the following F#
example, we create an instance of the main speech class and have the computer
say something:
open
speech
let sp =
speech.SpVoiceClass()
sp.Speak("I'll be back.",
SpeechVoiceSpeakFlags.SVSFDefault) |> ignore
While writing this chapter, I experimented with other COM
servers. What I found was that TLBIMP and F# converted all the required constructs
to F#-consumable types – very convenient!
P/Invoke
The last interop scenario that we’ll consider is using
platform invoke, more commonly referred to as P/Invoke. The P/Invoke plumbing,
which is an intrinsic feature of the CLR, enables F# to call unmanaged code,
e.g., code in the Windows API and other C-based DLLs.
To use data structures, classes and functions from external
libraries, e.g., the Windows API, you need to tell F# where these constructs
reside. This means telling F# the name of library, e.g., USER32.DLL, and the
names of the specific constructs you want to access. To do this, we open the System.Runtime.InteropServices
namespace and use the the DllImport attribute. The general syntax
is:
[<DllImport( arguments )>]
extern declaration
In the following example, we use P/Invoke to display a message box:
open
System.Runtime.InteropServices
[<DllImport("user32.dll")>]
extern
int MessageBox(int32, string, string, uint32)
// hwnd, text, caption, type
MessageBox(0, "Hello from F#!",
"F# Survival Guide", 0u)
DllImport tells F# where the external function
(or structure) lives. The extern keyword tells
the F# compiler that the referenced function is defined outside of the current
file or module. Together, DllImport plus extern
give us access to external constructs.
Given the C-based history of the Windows API, many API calls
take pointers as arguments. When dealing with APIs that take pointers as
arguments, we need to tell F# that the argument is a pointer via using the
asterisk symbol (*) in the function definition, and using the
address-of symbol (&&) when calling the function. In the
following example, we use F# to call the GetDiskFreeSpace
function defined in kernel32.dll.
open
System.Runtime.InteropServices
[<DllImport("kernel32.dll")>]
extern
bool GetDiskFreeSpace(
string lpRootPathName,
uint32* lpSectorsPerCluster,
uint32* lpBytesPerSector,
uint32* lpNumberOfFreeClusters,
uint32* lpTotalNumberOfClusters)
let mutable spc, bps, fc, tc = 0ul, 0ul, 0ul, 0ul
let ok =
GetDiskFreeSpace("c:\\",
&&spc, &&bps, &&fc, &&tc)
printfn
"Results %d %d %d %d" spc bps fc tc
System.Console.ReadLine()
Note that many Windows APIs accept structures and pointers
to structures as arguments. To call these APIs, you need to define F#-centric
definitions of the structures that you can pass into the API.
For example, let’s look at the GetLocalTime()
function defined in kernel32.dll. This function takes a pointer to a SYSTEMTIME
structure. F# does not ship with a definition of this structure, nor does it support
the concept of a “header file” from which to draw definitions. Therefore, we
need to define type-mappings ourselves, mapping F# types to C types. In the
following example, we demonstrate how to do this, using GetLocalTime()
as an archetype:
open
System
open
System.Runtime.InteropServices
[<Struct>]
type
SYSTIME =
val wYear: int16
val wMonth: int16
val wDayOfWeek: int16
val wDay: int16
val wHour: int16
val wMinute: int16
val wSecond: int16
val wMilliseconds: int16
[<DllImport("kernel32.dll")>]
extern void GetLocalTime(SYSTIME* t)
let mutable t = new
SYSTIME()
GetLocalTime(&&t)
printf
"Local date and time: "
printfn
"%d-%d-%d %02d:%02d" t.wYear
t.wMonth t.wDay t.wHour t.wMinute
Console.ReadLine() |> ignore
As you can see, we defined an F# structure that matches,
field for field, the C-based structured defined in the API documentation. We then
repeat the use of the technique of passing the function a pointer to the
relevant construct.
Whenever you shuttle types from .NET to unmanaged code,
you’re implicitly working with a .NET component called the marshaller. The
marshaller is responsible for shipping managed type to unmanaged code and vice
versa. The marshaller can handle many cases of type-shuttling transparently;
however, it’s not omnipotent, and occasionally needs some help. Some APIs, for
example, accept complex structures that themselves contain pointers, etc. When
working with these types, you may need to help the default marshaller via using
the MarshalAsAttribute
defined in System.Runtime.InteropServices.
This class tells the runtime how to shuttle and transform
data between the managed and unmanaged worlds. A full discussion of marshalling
complex data structures is beyond the scope of this “survival guide” text. For
more information, check out the MSDN documentation
on .NET marshalling via P/Invoke and the Web site http://www.pinvoke.net. It is a wiki-style
site that documents P/Invoke signatures, etc.
What You Need to Know
·
Namespaces enable you to broadly organize your code in a
consistent, cross-assembly manner. I recommend always defining your own
namespaces for any non-trivial project.
·
Modules provide a way to organize your code at the file level. F#
compiles modules to a class containing only static members.
·
Modules come in two flavors: file-level or “top-level” that
encompass the entire contents of a given file and “local” that help subdivide a
larger file into logical sections. Local modules can also be nested.
·
Modules may be aliased. This is usually done to abbreviate long
module names.
·
The load order of modules matters. Forward references do not
exist by default in F#.
·
Signature files provide a way to document and publish the public
interface to your module.
·
You can package your F# code in assemblies consisting of EXEs and
DLLs. This is no different from any other .NET language.
·
F# is interoperable with C# and other .NET languages.
·
F# can call into COM servers.
·
F# can call into the core Windows API via P/Invoke.
Please visit us at the CTO Corner (www.ctocorner.com) for more information and
an up-to-date blog.
|