As you saw in Chapter 3, you can use F# for pure functional programming. However, some issues, most notably I/O, are almost impossible to address without some kind of state change. F# does not require that you program in a stateless fashion. It allows you to use mutable identifiers whose values can change over time. F# also has other constructs that support imperative programming. You've already seen some in Chapter 3. Any example that wrote to the console included a few lines of imperative code alongside functional code. In this chapter, you'll explore these constructs, and many others, in much more detail.
First, you'll look at F#'s unit
type, a special type that means "no value," which enables some aspects of imperative programming. Next, you'll look at some of the ways F# can handle mutable state, that is, types whose values can change over time. These are the ref
type, mutable record types, and arrays. Finally, you'll look at using .NET libraries. The topics will include calling static methods, creating objects and working with their members, using special members such as indexers and events, and using the F# |>
operator, which is handy when dealing with .NET libraries.
Any function that does not accept or return values is of type unit
, which is similar to the type void
in C# and System.Void
in the CLR. To a functional programmer, a function that doesn't accept or return a value might not seem interesting, since if it doesn't accept or return a value, it does nothing. In the imperative paradigm, you know that side effects exist, so even if a function accepts or returns nothing, you know it can still have its uses. The unit
type is represented as a literal value, a pair of parentheses (()
). This means that whenever you want a function that doesn't take or return a value, you just put ()
in the code:
#light
let main() =
()
In this example, main
is a function because you placed parentheses after the identifier, where its parameters would go. If you didn't this, it would mean main
is not a function and instead just a value that is not a function. As you know, all functions are values, but here the difference between a function and a nonfunction value is important. If main
were a nonfunction value, the expressions within it would be evaluated only once. Since it is a function, the expressions will be evaluated each time it is called.
Caution Just because a function is named main
doesn't mean it is the entry point of the program and is executed automatically. If you wanted your main function to be executed, then you would need to add a call to main()
at the end of the source file. Chapter 6 details exactly how the entry point is determined for an F# program.
Similarly, by placing ()
after the equals sign, you tell the compiler you are going to return nothing. Ordinarily, you need to put something between the equals sign and the empty parentheses, or the function is pointless; however, for the sake of keeping things simple, I'll leave this function pointless. Now you'll see the type of main
by using the fsc -i
switch; the results of this are as follows. (I explained the notation used by the compiler's -i
switch in Chapter 3's "Types and Type Inference.") As you can see, the type of main
is a function that accepts unit
and transforms it into a value of type unit
:
val main : unit -> unit
Because the compiler now knows the function doesn't return anything, you can now use it with some special imperative constructs. To call the function, you can use the let
keyword followed by a pair of parentheses and the equals sign. This is a special use of the let
keyword, which means "call a function that does not return a value." Alternatively, you can simply call the function without any extra keywords at all:
#light
let () = main()
// -- or --
main()
Similarly, you can chain functions that return unit
together within a function—simply make sure they all share the same indentation. The next example shows several print_endline
functions chained together to print text to the console:
#light
let poem() =
print_endline "I wandered lonely as a cloud"
print_endline "That floats on high o'er vales and hills,"
print_endline "When all at once I saw a crowd,"
print_endline "A host, of golden daffodils"
poem()
It's not quite true that the only functions that return unit
type can be used in this manner; however, using them with a type other than unit
will generate a warning, which is something most programmers want to avoid. So, to avoid this, it's sometimes useful to turn a function that does return a value into a function of type unit
, typically because it has a side effect. The need to do this is fairly rare when just using F# libraries written in F# (although situations where it is useful do exist), but it is more common when using .NET libraries that were not written in F#.
The next example shows how to turn a function that returns a value into a function that returns unit
:
#light
let getShorty() = "shorty"
let _ = getShorty()
// -- or --
ignore(getShorty())
// -- or --
getShorty() |> ignore
First you define the function getShorty
, which returns a string. Now imagine, for whatever reason, you want to call this function and ignore its result. The next two lines demonstrate different ways to do this. First, you can use a let
expression with an underscore (_
) character in place of the identifier. The underscore tells the compiler this is a value in which you aren't interested. Second, this is such a common thing to do that it has been wrapped into a function, ignore
, which is available in the F# base libraries and is demonstrated on the third line. The final line shows an alternative way of calling ignore
using the pass-forward operator to pass the result of getShorty()
to the ignore
function. I explain the pass-forward operator in the "The |> Operator" section.
In Chapter 3 I talked about how you could bind identifiers to values using the keyword let
and noted how under some circumstances you could redefine and rebound, but not modify, these identifiers. If you want to define an identifier whose value can change over time, you can do this using the mutable
keyword. A special operator, the left ASCII arrow (or just left arrow), is composed of a less-than sign and a dash (<-
) and is used to update these identifiers. An update operation using the left arrow has type unit
, so you can chain these operations together as discussed in the previous section. The next example demonstrates defining a mutable identifier of type string
and then changing the changing the value it holds:
#light
let mutable phrase = "How can I be sure, "
print_endline phrase
phrase <- "In a world that's constantly changing"
print_endline phrase
The results are as follows:
How can I be sure,
In a world that's constantly changing
At first glance this doesn't look too different from redefining an identifier, but it has a couple of key differences. When you use the left arrow to update a mutable identifier, you can change its value but not its type—when you redefine an identifier, you can do both. A compile error is produced if you try to change the type; the next example demonstrates this:
#light
let mutable number = "one"
phrase <- 1
When attempting to compile this code, you'll get the following error message:
Prog.fs(9,10): error: FS0001: This expression has type
int
but is here used with type
string
The other major difference is where these changes are visible. When you redefine an identifier, the change is visible only within the scope of the new identifier. When it passes out of scope, it reverts to its old value. This is not the case with mutable identifiers. Any changes are permanent, whatever the scope. The next example demonstrates this:
#light
let redefineX() =
let x = "One"
printfn "Redefining:
x = %s" x
if true then
let x = "Two"
printfn "x = %s" x
else ()
printfn "x = %s" x
let mutableX() =
let mutable x = "One"
printfn "Mutating:
x = %s" x
if true then
x <- "Two"
printfn "x = %s" x
else ()
printfn "x = %s" x
redefineX()
mutableX()
The results are as follows:
Redefining:
x = One
x = Two
x = One
Mutating:
x = One
x = Two
x = Two
Identifiers defined as mutable are somewhat limited because they can't be used within a subfunction. You can see this in the next example:
#light
let mutableY() =
let mutable y = "One"
printfn "Mutating:
x = %s" y
let f() =
y <- "Two"
printfn "x = %s" y
f()
printfn "x = %s" y
The results of this example, when compiled and executed, are as follows:
Prog.fs(35,16): error: The mutable variable 'y' has escaped its scope. Mutable
variables may not be used within an inner subroutine. You may need to use a heap-
allocated mutable reference cell instead, see 'ref' and '!'.
As the error messages says, this is why the ref
type, a special type of mutable record, has been made available—to handle mutable variables that need to be shared among several functions. I discuss mutable records in the next section and the ref
type in the section after that.
In Chapter 3, when you first met record types, I did not discuss how to update their fields. This is because record types are immutable by default. F# provides special syntax to allow the fields in record types to be updated. You do this by using the keyword mutable
before the field in a record type. I should emphasize that this operation changes the contents of the record's field rather than changing the record itself.
#light
type Couple = { her : string ; mutable him : string }
let theCouple = { her = "Elizabeth Taylor " ; him = "Nicky Hilton" }
let print o = printf "%A
" o
let changeCouple() =
print theCouple;
theCouple.him <- "Michael Wilding";
print theCouple;
theCouple.him <- "Michael Todd";
print theCouple;
theCouple.him <- "Eddie Fisher";
print theCouple;
theCouple.him <- "Richard Burton";
print theCouple;
theCouple.him <- "Richard Burton";
print theCouple;
theCouple.him <- "John Warner";
print theCouple;
theCouple.him <- "Larry Fortensky";
print theCouple
changeCouple()
The results are as follows:
{her = "Elizabeth Taylor "; him = "Nicky Hilton"}
{her = "Elizabeth Taylor "; him = "Michael Wilding"}
{her = "Elizabeth Taylor "; him = "Michael Todd"}
{her = "Elizabeth Taylor "; him = "Eddie Fisher"}
{her = "Elizabeth Taylor "; him = "Richard Burton"}
{her = "Elizabeth Taylor "; him = "Richard Burton"}
{her = "Elizabeth Taylor "; him = "John Warner"}
{her = "Elizabeth Taylor "; him = "Larry Fortensky"}
This example shows a mutable record in action. A type, couple
, is defined where the field him
is mutable but the field her
is not. Next, an instance of couple
is initialized, and then you change the value of him
many times, each time displaying the results. I should note that the mutable
keyword applies per field, so any attempt to update a field that is not mutable will result in a compile error; for example, the next example will fail on the second line:
#light
theCouple.her <- "Sybil Williams";
print_any theCouple
When attempting to compile this program, you'll get the following error message:
prog.fs(2,4): error: FS0005: This field is not mutable
The ref
type is a simple way for a program to use mutable state, that is, values that change over time. The ref
type is just a record type with a single mutable field that is defined in the F# libraries. Some operators are defined to make accessing and updating the field as straightforward as possible. F#'s definition of the ref
type uses type parameterization, a concept introduced in the previous chapter, so although the value of the ref
type can be of any type, you cannot change the type of the value once you have created an instance of the value.
Creating a new instance of the ref
type is easy; you use the keyword ref
followed by whatever item represents the value of ref
. The next example is the compiler's output (using the -i
option, which shows that the type of phrase
is string ref
, meaning a reference type that can contain only strings):
let phrase = ref "Inconsistency"
val phrase : string ref
This syntax is similar to defining a union type's constructors, also shown in the previous chapter. The ref
type has two built-in operators to access it; the exclamation point (!
) provides access to the value of the reference type, and an operator composed of a colon followed by an equals sign (:=
) provides for updating it. The !
operator always returns a value of the type of the contents of the ref
type, known to the compiler thanks to type parameterization. The :=
operator has type unit
, because it doesn't return anything.
The next example shows how to use a ref
type to total the contents of an array. On the third line of totalArray
, you see the creation of the ref
type. In this case, it is initialized to hold the value 0
. On the fifth line, you see the ref
type being both accessed and updated. First, !
is used to access the value with the ref
type; then, after it has been added to the current value held in the array, the value of the ref
type is updated through the use of the :=
operator. Now the code will correctly print 6
to the console.
#light
let totalArray () =
let a = [| 1; 2; 3 |]
let x = ref 0
for n in a do
x := !x + n
print_int !x
print_newline()
totalArray()
The result is as follows:
6
Caution If you are used to programming in one of the C family of programming languages, you should be careful here. When reading F# code, it is quite easy to misinterpret the ref
type's !
operator as a Boolean "not" operator. F# uses a function called not
for Boolean "not" operations.
The ref
type is a useful way to share mutable values between several functions. An identifier can be bound to a ref
type defined in scope that is common to all functions that want to use the value; then the functions can use the value of the identifier as they like, changing it or merely reading it. Because in F# functions can be passed around as if they were values, everywhere the function goes, the value follows it. This process is known as capturing a local or creating a closure. The next example demonstrates this by defining three functions, inc
, dec
, and show
, which all share a common ref
type holding an integer. The functions inc
, dec
, and show
are all defined in their own private scopes and then returned to the top level as a tuple so they are visible everywhere. Note how n
is not returned; it remains private, but inc
, dec
, and show
are all still able to access n
. This is a useful technique for controlling what operations can take place on mutable data.
#light
let inc, dec, show =
let n = ref 0
let inc () =
n := !n + 1
let dec () =
n := !n - 1
let show () =
print_int !n
inc, dec, show
inc()
inc()
dec()
show()
The result is as follows:
1
Arrays are a concept that most programmers are familiar with, since almost all programming languages have some sort of array type. The F# array type is based on the BCL System.Array
type, so anyone who has used in arrays in C# or Visual Basic will find that the underlying concepts are the same.
Arrays are a mutable collection type in F#. Arrays are the opposite of lists, discussed in Chapter 3. The values within arrays are updatable, whereas lists are not, and lists can grow dynamically, whereas arrays cannot. One-dimensional arrays are sometimes referred to as vectors, and multidimensional arrays are sometimes called matrices. Arrays are defined by a sequence of items separated by semicolons (;
) and delimited by an opening square bracket and a vertical bar ([|
) and a closing bar and square bracket (|]
). The syntax for referencing an array element is the name of the identifier of the array followed by period (.
) and then the index of the element in square brackets ([]
). The syntax for retrieving the value of an element stops there. The syntax for setting the value of an element is the left arrow (<-
) followed by the value to be assigned to the element.
The next example shows an array being read from and written to. First an array, rhymeArray
, is defined, and then you read all the members from it. Then you insert new values into the array, and finally you print out all the values you have.
#light
let rhymeArray =
[| "Went to market" ;
"Stayed home" ;
"Had roast beef" ;
"Had none" |]
let firstPiggy = rhymeArray.[0]
let secondPiggy = rhymeArray.[1]
let thirdPiggy = rhymeArray.[2]
let fourthPiggy = rhymeArray.[3]
rhymeArray.[0] <- "Wee,"
rhymeArray.[1] <- "wee,"
rhymeArray.[2] <- "wee,"
rhymeArray.[3] <- "all the way home"
print_endline firstPiggy
print_endline secondPiggy
print_endline thirdPiggy
print_endline fourthPiggy
print_any rhymeArray
The results of this example, when compiled and executed, are as follows:
Went to market
Stayed home
Had roast beef
Had none
[|"Wee,"; "wee,"; "wee,"; "all the way home"|]
Arrays, like lists, use type parameterization, so the type of the array is the type of its contents followed by the array's type, so rhymeArray
has type string array
, which may also be written string[]
.
Multidimensional arrays in F# come in two slightly different flavors, jagged and rectangular. Jagged arrays, as the name suggests, are arrays where the second dimension is not a regular shape. They are simply arrays whose contents happen to other arrays, and the length of the inner arrays is not forced to be the same. In rectangular arrays, all inner arrays are of the same length; in fact, there is really no concept of an inner array since the whole array is just the same object. The method of getting and setting items in the two different types of arrays differs slightly.
For jagged arrays, you use the period followed by the index in parentheses, but you have to use this twice (one time for each dimension), because the first time you get back the inner array and the second time you get the element within it.
The next example demonstrates a simple jagged array, called jagged
. The array members are accessed in two different ways. The first inner array (at index 0
) is assigned to the identifier singleDim
, and then its first element is assigned to itemOne
. On the fourth line, the first element of the second inner array is assigned to itemTwo
, using one line of code.
#light
let jagged = [| [| "one" |] ; [| "two" ; "three" |] |]
let singleDim = jagged.[0]
let itemOne = singleDim.[0]
let itemTwo = jagged.[1].[0]
printfn "%s %s" itemOne itemTwo
The results of this example, when compiled and executed, are as follows:
one two
To reference elements in rectangular arrays, use a period (.
) followed by all the indexes in square brackets, separated by commas. Unlike jagged arrays, which are multidimensional but can be defined using the same ([||]
) syntax as single-dimensional arrays, you must create rectangular arrays with the create
function of the Array2
and Array3
modules, which support two- and three-dimensional arrays, respectively. This doesn't mean rectangular arrays are limited to three dimensions, because it's possible to use the System.Array
class to create rectangular arrays with more than three dimensions; however, creating such arrays should be considered carefully, because adding extra dimensions can quickly lead to very large objects.
The next example creates a rectangular array, square
. Then its elements are populated with the integers 1, 2, 3, and 4.
#light
let square = Array2.create 2 2 0
square.[0,0] <- 1
square.[0,1] <- 2
square.[1,0] <- 3
square.[1,1] <- 4
printf "%A
" square
Now let's look at the differences between jagged and rectangular arrays. First create a jagged array to represent Pascal's Triangle:
#light
let pascalsTriangle = [|
[|1|];
[|1; 1|];
[|1; 2; 1|];
[|1; 3; 3; 1|];
[|1; 4; 6; 4; 1|];
[|1; 5; 10; 10; 5; 1|];
[|1; 6; 15; 20; 15; 6; 1|];
[|1; 7; 21; 35; 35; 21; 7; 1|];
[|1; 8; 28; 56; 70; 56; 28; 8; 1|];
|]
Then create a rectangular array that contains various number sequences that are hidden within pascalsTriangle
:
let numbers =
let length = (Array.length pascalsTriangle) in
let temp = Array2.create 3 length 0 in
for index = 0 to length - 1 do
let naturelIndex = index - 1 in
if naturelIndex >= 0 then
temp.[0, index] <- pascalsTriangle.[index].[naturelIndex];
let triangularIndex = index - 2 in
if triangularIndex >= 0 then
temp.[1, index] <- pascalsTriangle.[index].[triangularIndex];
let tetrahedralIndex = index - 3 in
if tetrahedralIndex >= 0 then
temp.[2, index] <- pascalsTriangle.[index].[tetrahedralIndex]
done
temp
Then display the sequences you've retrieved:
print_any numbers
The results of this example, when compiled and executed, are as follows:
[|[|0; 1; 2; 3; 4; 5; 6; 7; 8|];
[|0; 0; 1; 3; 6; 10; 15; 21; 28|];
[|0; 0; 0; 1; 4; 10; 20; 35; 56|]|]
The following shows the types displayed when you use the compiler's -i
switch:
val pascals_triangle : int array array
val numbers : int [,]
As you may expect, jagged and rectangular arrays have different types. The type of a jagged array is the same as a single-dimensional array, just with an array per dimension, so the type of pascalsTriangle
is int array array
. Rectangular arrays use a more C#-style notation. First comes the name of the type of the array's elements, and after that comes square brackets ([]
) with one comma for every dimension greater than 1, so the type of the example two-dimensional numbers
array is int[,]
.
Caution To write code that is compatible with both .NET 1.1 and 2.0, you must use the Microsoft.FSharp.Compatibility
namespace's CompatArray
and CompatMatrix
types. This is because of differences in the way that arrays behave in different .NET versions. In .NET 1.1, arrays are pseudogeneric, because you can create arrays of different types as though they were generic; however, they are not really generic, since .NET 1.1 doesn't support generics. In .NET 2.0, arrays became properly generic, and their behavior changed in a number of subtle ways that F# cannot abstract away. The result is that you must explicitly choose whether you want to use arrays that are supported in .NET 1.1.
I introduced compression syntax in Chapter 3 for lists and sequences. You can use a corresponding syntax to create arrays. The only difference between this and the functional-style syntax is the characters that delimit the array. You use vertical bars surrounded by square brackets for arrays:
#light
let chars = [| '1' .. '9' |]
let squares =
[| for x in 1 .. 9
-> x, x*x |]
printfn "%A" chars
printfn "%A" squares
The results are as follows:
[|'1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'|]
[|(1, 1); (2, 4); (3, 9); (4, 16); (5, 25); (6, 36); (7, 49); (8, 64); (9, 81)|]
Unlike the pseudo-control-flow syntax described in Chapter 3, F# does have some imperative control-flow constructs. In addition to the imperative use of if
, there are also while
and for
loops.
The major difference from using the if
expression in the imperative style, that is, using it with a function that returns type unit
, is that you aren't forced to use an else
, as the next example demonstrates:
#light
if System.DateTime.Now.DayOfWeek = System.DayOfWeek.Sunday then
print_endline "Sunday Playlist: Lazy On A Sunday Afternoon - Queen"
Though it isn't necessary to have an else
expression if the if
expression has type unit
, you can add one if necessary. This too must have type unit
, or the compiler will issue an error. The next example demonstrates this:
#light
if System.DateTime.Now.DayOfWeek = System.DayOfWeek.Monday then
print_endline "Monday Playlist: Blue Monday - New Order"
else
print_endline "Alt Playlist: Fell In Love With A Girl - White Stripes"
You can use whitespace to detect where an if
expression ends. The code that belongs to the if
expression is indented, and the if
expression ends when it goes back to its original indentation. So, in the next example, the string "Tuesday Playlist: Ruby Tuesday - Rolling Stones"
will be printed on a Tuesday, and "Everyday Playlist: Eight Days A Week - Beatles"
will be printed every day of the week.
#light
if System.DateTime.Now.DayOfWeek = System.DayOfWeek.Tuesday then
print_endline "Tuesday Playlist: Ruby Tuesday - Rolling Stones"
print_endline "Everyday Playlist: Eight Days A Week - Beatles"
If you want multiple statements to be part of the if
statement, then you would simply give them the same indention, as shown in the next example where both strings will be printed only on a Friday:
#light
if System.DateTime.Now.DayOfWeek = System.DayOfWeek.Friday then
print_endline "Friday Playlist: Friday I'm In Love - The Cure"
print_endline "Friday Playlist: View From The Afternoon - Arctic Monkeys"
Most programmers are familiar with for
loops because they are commonly found in imperative programming languages. The idea of a for
loop is to declare an identifier, whose scope is the for
loop, that increases its value by 1 after each iteration of the loop and provides the condition for loop termination. F# follows this syntax. It starts with the keyword for
followed by the identifier that will hold the counter value; then comes an equals sign, followed by an expression for the initial counter value, then the keyword to
, and then an expression for the terminal value. The code that forms the body of the for
loop comes after this, sandwiched between the keywords do
and done
. The for
loop has type unit
, so the code that forms the body of the loop should have type unit
; otherwise, the compiler will issue a warning.
The next example demonstrates a common usage of a for
loop—to enumerate all the values in an array. The identifier index
will take on values starting at 0
and ending at 1 less than the length of the array. You can use this identifier as the index for the array.
#light
let ryunosukeAkutagawa = [| "Green "; "frog, ";
"Is "; "your "; "body "; "also ";
"freshly "; "painted?" |]
for index = 0 to Array.length ryunosukeAkutagawa - 1 do
print_string ryunosukeAkutagawa.[index]
The results of this example, when compiled and executed, are as follows:
Green frog, Is your body also freshly painted?
In a regular for
loop, the initial value of the counter must always be less than the final value, and the value of the counter will increase as the loop continues. There is a variation on this, where to
is replaced by downto
. In this case, the initial counter value must always be greater than the final value, and the counter will decrease as the loop continues. An example of how to use downto
is as follows:
#light
let shusonKato = [| "watching."; "been "; "have ";
"children "; "three "; "my "; "realize "; "and ";
"ant "; "an "; "kill "; "I ";
|]
for index = Array.length shusonKato - 1 downto 0 do
print_string shusonKato.[index]
The results of this example, when compiled and executed, are as follows:
I kill an ant and realize my three children have been watching.
The while
loop is another familiar imperative language construct. It is an expression that creates a loop over a section of code until a Boolean expression changes to false
. To create a while
loop in F#, you use the keyword while
followed by a Boolean expression that determines whether the loop should continue. As with for
loops, you place the body of the loop between the keywords do
and done
, and the body should have type unit
; otherwise, the compiler will issue a warning. Here's an example of a while
loop:
#light
let matsuoBasho = ref [ "An "; "old "; "pond! ";
"A "; "frog "; "jumps "; "in- ";
"The "; "sound "; "of "; "water" ]
while (List.nonempty !matsuoBasho) do
print_string (List.hd !matsuoBasho);
matsuoBasho := List.tl !matsuoBasho
You enumerate over a list, and the Boolean expression to terminate the loop is based on whether the list is empty. Within the body of the loop, you print the head of the list and then remove it, shortening the list on each iteration.
The results of this example, when compiled and executed, are as follows:
An old pond! A frog jumps in- The sound of water
You can use loops using for
to enumerate collections, performing an imperative action, one that returns unit
, on each element. This is similar to the foreach
loop available in many programming languages. The syntax for using a comprehension to enumerate a collection is the for
keyword followed by the identifier that will be bound to each item in the collection, then the collection, and then the keyword do
. The code for processing each item in the collection comes next—indented to show it belongs to the for
loop. The following example demonstrates this, enumerating an array of strings and printing each one:
#light
let words = [| "Red"; "Lorry"; "Yellow"; "Lorry" |]
for word in words do
print_endline word
The results are as follows:
Red
Lorry
Yellow
Lorry
As you'll see later in this chapter, and in many examples throughout the book, this can be a convenient way to work with collections returned by .NET BCL methods.
One extremely useful feature of imperative programming in F# is being able to use just about any library written in a .NET programming language, including the many methods and classes available as part of the BCL itself. I consider this to be imperative programming, because libraries written in other languages make no guarantees about how state works inside them, so you can't know whether a method you call has side effects.
A distinction should be made between calling libraries written in F# and libraries written in any other language. This is because libraries written in F# have metadata that describes extra details about the library, such as whether a method takes a tuple or whether its parameters can be curried. This metadata is specific to F# and in a binary form understood by the F# compiler. This is largely why the Microsoft.FSharp.Reflection
API is provided—to bridge the gap between F# and .NET metadata.
The basic syntax when calling static or instance properties or methods is the same. Method calls to a non-F# library must have their arguments separated by commas and surrounded by parentheses. (Remember, F# function calls usually use whitespace to separate arguments, and parentheses are needed only to impose precedence.) Method calls to a non-F# library cannot be curried, and the methods themselves are not equivalent to values, so they cannot be passed as arguments. Despite this difference, calling a method from a non-F# library is pretty straightforward. You'll start off by using static properties and methods:
#light
open System.I0
if File.Exists("test.txt") then
print_endline "Text file "test.txt" is present"
else
print_endline "Text file "test.txt" does not exist"
This example calls a static method from the .NET Framework BCL. Calling a static method is almost identical to calling an F# function. First comes the class name followed by a period (.
) and then the name of the method; the only real difference is in the syntax for passing the arguments, which are surrounded by parentheses and separated by commas. You make a call to the System.IO.File
class's Exists
method to test whether a file exists and print an appropriate message depending on the result.
Often, you'll want to use the functionality of an existing .NET method but also want to use it in a functional manner. A common pattern in F# to achieve this is to import the function by writing a thin .NET wrapper. The next example demonstrates this:
#light
open System.IO
let exists filePath = File.Exists(filePath)
let files = ["test1.txt"; "test2.txt"; "test3.txt"]
let results = List.map exists files
print_any results
Unless you've created these specific text files in the directory where it runs, your result will be [false; false; false]
. You have used the BCL Exists
method to test whether a list of files exists. On the first line, you create a function that wraps the Exists
method call, so you can use it in a functional manner and pass it to the map
function.
When using .NET methods with lots of arguments, it can sometimes be helpful to know the names of the arguments to help you keep track of what each argument is doing. F# lets you use named arguments, where you give the name of the argument, then an equals sign, and then the value of the argument. The following example demonstrates this with an overload of File.Open()
that takes four arguments:
#light
open System.IO
let file = File.Open(path = "test.txt",
mode = FileMode.Append,
access = FileAccess.Write,
share = FileShare.None)
file.Close()
Using classes from non-F# libraries is also straightforward. The syntax for instantiating an object consists of the keyword new
, then the name of the class to be instantiated, and then constructor arguments separated by commas within parentheses. You can use the let
keyword to bind an instance of a class to an identifier. Once associated with an identifier, the object behaves a lot like a record type; the object referred to cannot be changed, but its contents can. Also, if the identifier is not at the top level, then it can be redefined or hidden by an identifier of the same name in another scope. Accessing fields, properties, events, and methods should be pretty intuitive to both C# and Visual Basic programmers because the syntax is similar. To access any member, you use the identifier of the object followed by a period (.
) and then the name of the member. Arguments to instance methods follow the same convention as for static methods, and they must be within parentheses and separated by commas. To retrieve the value of a property or field, only the name of member is needed, and to set it, you use the left arrow (<-
).
The next example demonstrates how to create a System.IO.FileInfo
object and then use various members of the class to manipulate it in different ways. On the first line, you make the System.IO
namespace available to F#; then on the second, you create the FileInfo
object, passing it the name of the file in which you're interested. Then you check whether the file exists using the Exists
instance property. If it doesn't exist, you create a new file using the CreateText()
instance method and then set it to be read-only using the Attributes
instance property. The next example uses the using
function to clean up resources. I explain this fully in the section "Microsoft.FSharp.Core.Operators" in Chapter 7.
#light
open System.IO
let file = new FileInfo("test.txt")
if not file.Exists then
using (file.CreateText()) (fun stream ->
stream.WriteLine("hello world"))
file.Attributes <- FileAttributes.ReadOnly
print_endline file.FullName
F# also lets you to set properties when you're constructing the object. It's quite common to set object properties as part of the process of initially configuring the object, especially in WinForms programming (see Chapter 8 for more information about WinForms). To set a property at construction time, place the property name inside the constructor followed by an equals sign and then by the value for the property. Separate multiple properties with commas. The following is a variation on the previous example; it sets the ReadOnly
attribute when the object is the constructor:
#light
open System.IO
let filename = "test.txt"
let file =
if File.Exists(filename) then
Some(new FileInfo(filename, Attributes = FileAttributes.ReadOnly))
else
None
Note that you need to test for the file's existence to avoid a runtime exception when trying to set the Attributes
property. F# allows you to set type parameters when calling a constructor, because it is not always possible to infer the type parameter of when making a constructor call. The type parameters are surrounded by angle brackets (<>
) and separated by commas. The next example demonstrates how to set a type parameter when calling a constructor. You can create an instance of System.Collections.Generic.List
, which can be used only with integers by setting its type parameter when it is created. In F# System.Collections.Generic.List
is called ResizeArray
to avoid confusion with F# lists.
#light
open System
let intList =
let temp = new ResizeArray<int>() in
temp.AddRange([| 1 ; 2 ; 3 |]);
temp
intList.ForEach( fun i -> Console.WriteLine(i) )
The results are as follows:
1
2
3
The previous example also demonstrates another nice feature of F# when interoperating with non-F# libraries. .NET APIs often use a .NET construct called delegates, which are conceptually a kind of function value. F# functions will automatically be converted to .NET delegate objects if their signatures match. You can see this on the last line, where an F# function is passed directly to a method that takes a .NET delegate type.
To keep methods as flexible as possible, you may prefer not to specify a type parameter when importing methods that take generic delegates or perhaps when you're creating a wrapper F# function around constructors for a non-F# library. You achieve this by using the underscore (_
) in place of the type parameter, as in the first line of the next example. (The following example uses the forward operator, |>
, which I explain in the "The |> Operator" section.)
#light
open System
let findIndex f arr = Array.FindIndex(arr, new Predicate<_>(f))
let rhyme = [|"The"; "cat"; "sat"; "on"; "the"; "mat" |]
printfn "First word ending in 'at' in the array: %i"
(rhyme |> findIndex (fun w -> w.EndsWith("at")))
The results of this example, when compiled and executed, are as follows:
First word ending in 'at' in the array: 1
Here you import the FindIndex
method from the System.Array
class, so you can use it in a curried style. If you had not explicitly created a delegate, the identifier f
would have represented a predicate delegate rather than a function, meaning all calls to findIndex
would need to explicitly create a delegate object, which is not ideal. However, if you had specified a type when creating the Predicate
delegate in the definition of findIndex
, then you would have limited the use of the findIndex
function to arrays of a specific type. Occasionally, this may be what you want to do, but it is not usually the case. By using the underscore, you avoid having to specify a type for the findIndex
function, keeping it nice and flexible.
Indexers are a .NET concept that are designed to make a collection class look more like an array. Under the hood an indexer is a special property that is always called Item
and has one or more parameters. It is important you have easy access to an indexer property, because many classes within the BCL have indexers.
In respect to syntax, F# offers two ways of using an indexer. You can explicitly use the Item
property, or you can use an array-like syntax, with brackets instead of parentheses around the index.
open System.Collections.Generic
let stringList =
let temp = new ResizeArray<string>() in
temp.AddRange([| "one" ; "two" ; "three" |]);
temp
let itemOne = stringList.Item(0)
let itemTwo = stringList.[1]
printfn "%s %s" itemOne itemTwo
This example associates the strings "one"
and "two"
with the identifiers itemOne
and itemTwo
, respectively. The association of "one"
with itemOne
demonstrates explicitly using the Item
property. The association of "two"
with itemTwo
uses the bracket syntax.
Note This example also demonstrates a common pattern in F#. Note how you want to create the identifier stringList
as an object from a non-F# library and at the same time initialize it to a certain state. To do this you assign the object to a temporary identifier and then call an instance member on the object to manipulate its state. Finally, you return the temporary identifier so it becomes the value of stringList
. In this way, you keep the object creation and initialization logic close together.
Events are special properties of objects that allow functions to be attached to them. The functions that are attached to events are sometimes referred to as handlers. When the event occurs, it executes all the functions that have been attached to it. An example of this might be that a Button
object exposes a Click
event, which occurs when a user clicks the button. This would mean that any functions that have been attached to the button's Click
event would execute when the button is clicked. This is extremely useful, since it's common to need notifications of what the user has done when creating user interfaces.
Adding a hander to an event is fairly straightforward. Each event exposes a method called Add
, and the handling event is passed to this method. Events come from non-F# libraries, so the Add
method follows the convention that its arguments must be surrounded by parentheses. In F# it is common to place the handler function inside the Add
method itself using F#'s anonymous function feature. The type of the handler function must match the type of the Add
method's parameter, and this parameter has type 'a -> unit
. This means that for events exposed by objects in the BCL, the parameter of the Add
method will have a type similar to EventArgs -> Unit
.
The next example shows the creation of a Timer
object and a function being added to the timer's Elapsed
event. A Timer
object is an object that will fire its Elapsed
event at regular intervals. In this case, the handler will show a message box displaying a notice to the user. Notice how you do not care about the argument that will be passed to the handler function, so you ignore it using the underscore.
#light
open System.Timers
module WF = System.Windows.Forms
let timer =
let temp = new Timer()
temp.Interval <- 3000.0
temp.Enabled <- true
let messageNo = ref 0
temp.Elapsed.Add(fun _ ->
let messages = ["bet"; "this"; "gets";
"really"; "annoying"; "very"; "quickly";]
WF.MessageBox.Show(List.nth messages !messageNo) |> ignore
messageNo := (!messageNo + 1) % (List.length messages))
temp
print_endline "Whack the return to finish!"
read_line() |> ignore
timer.Enabled <- false
It is also possible to remove handlers from events. To do this, you must keep the function you are going to add to the event in scope; you can pass it to the event's RemoveHandler
method. The RemoveHandler
method accepts a delegate, which is an object that wraps a regular .NET method to allow it to be passed around like a value. This means the handler function must be given to the event already wrapped in a delegate and must therefore use the event's AddHandler
(or Removehandler
) method instead of its Add
(or Remove
) method. Creating a delegate in F# is straightforward. You simply call the delegate's constructor, the same way you call any constructor for an object from any non-F# library, passing it the function that delegate should wrap.
#light
open System
open System.Windows.Forms
let form =
let temp = new Form()
let stuff _ _ = ignore(MessageBox.Show("This is "Doing Stuff""))
let stuffHandler = new EventHandler(stuff)
let event = new Button(Text = "Do Stuff", Left = 8, Top = 40, Width = 80)
event.Click.AddHandler(stuffHandler)
let eventAdded = ref true
let label = new Label(Top = 8, Left = 96)
let setText b = label.Text <- (Printf.sprintf "Event is on: %b" !b)
setText eventAdded
let toggle = new Button(Text = "Toggle Event", Left = 8, Top = 8, Width = 80)
toggle.Click.Add(fun _ ->
if !eventAdded then
event.Click.RemoveHandler(stuffHandler)
else
event.Click.AddHandler(stuffHandler)
eventAdded := not !eventAdded
setText eventAdded)
let dc c = (c :> Control)
temp.Controls.AddRange([| dc toggle; dc event; dc label; |]);
temp
do Application.Run(form)
This example shows the creation of a simple WinForm in F#. Events are synonymous with user interface programming, so I thought it would be good to show an example event of events being used in this context. Near the beginning of the example, you create a delegate, stuffHandler
, which is then added to the Click
event on the button event
. Later you add a handler directly to the toggle button's Click
event, which adds or removes the handler from the button's event.
Caution The previous sample will not work in the F# interactive console, fsi
, because of the call to Application.Run
. Users of fsi
should replace this with form.Visible <- true;;
.
As you saw in Chapter 3, pattern matching is a powerful feature of F#. Pattern matching allows a programmer to specify that different computations are executed depending on some value. F# has a construct that allows pattern matching over .NET types. The rule to match a .NET type is formed from a colon and question mark operator (:?
) followed by the name of the .NET type to be matched. Because it is impossible have an exhaustive list of .NET types, you must always provide a default rule when pattern matching over .NET types.
#light
let simpleList = [ box 1; box 2.0; box "three" ]
let recognizeType (item : obj) =
match item with
| :? System.Int32 -> print_endline "An integer"
| :? System.Double -> print_endline "A double"
| :? System.String -> print_endline "A string"
| _ -> print_endline "Unknown type"
List.iter recognizeType simpleList
The results are as follows:
An integer
A double
A string
This example shows a function, recognizeType
, that is designed to recognize three of the .NET basic types via pattern matching. This function is then applied to a list. A couple of details about this function are noteworthy. First, the function takes an argument of type obj
, and you need to use a type annotation to make sure it does. If you didn't use the type annotation, the compiler would infer that the function can take any type and would use type 'a
. This would be a problem, because you cannot use pattern matching of this kind over F#'s types, but only over .NET types. Second, the function's default case uses the underscore to ignore the value.
Once you've recognized that a value is of a certain type, it's common to want to be able to do something with that value. To be able to use the value on the right side of a rule, you can use the as
keyword followed by an identifier. You can see this in the next example, where you rewrite recognizeType
to include the value in the message that is printed when a type is recognized:
#light
let anotherList = [ box "one"; box 2; box 3.0 ]
let recognizeAndPrintType (item : obj) =
match item with
| :? System.Int32 as x -> printfn "An integer: %i" x
| :? System.Double as x -> printfn "A double: %f" x
| :? System.String as x -> printfn "A string: %s" x
| x -> printfn "An object: %A" x
List.iter recognizeAndPrintType anotherList
The results of this example, when compiled and executed, are as follows:
A string: one
An integer: 2
A double: 3.000000
Notice how for a final default rule you just use an identifier. You don't need to match it to a type since you already know it will be of type obj
, since the value being matched over is already of type obj
. In fact, if you try to create a rule where you match with type obj
, you will get a compile error since this rule will always match. You can see this in the next example, where the previous example is reworked to generate the compile error I'm talking about:
#light
let thirdList = [ ("one" :> obj); (2 :> obj); (3.0 :> obj) ]
let reconizeTypeWrongly (item : obj) =
match item with
| :? System.Int32 -> print_endline "An integer"
| :? System.Double -> print_endline "A double"
| :? System.String -> print_endline "A string"
| :? System.Object -> print_endline "Unknown type"
List.iter reconizeTypeWrongly thirdList
Pattern matching over .NET types is also useful for handling exceptions thrown by .NET methods. The pattern match rules are formed in the same way except they are used with the try ... with
construct instead of the try ... match
construct. The next example shows two .NET exceptions being thrown and then caught. The exceptions thrown are pattern matched over, and a different message is printed to the console depending on the type of exception thrown.
#light
try
if System.DateTime.Now.Second % 3 = 0 then
raise (new System.Exception())
else
raise (new System.ApplicationException())
with
| :? System.ApplicationException ->
print_endline "A second that was not a multiple of 3"
| _ ->
print_endline "A second that was a multiple of 3"
The pass-forward, pipe-forward, or just forward operator (|>
) is useful when working with .NET libraries. This is because it helps the compiler infer the correct types for method parameters without the need for explicit type annotations.
To understand why this operator is useful, it is helpful to probe a little deeper into to how right-to-left type inference works. Consider the following simple example, where you define a list of integers, called intList
, of type int list
and then pass this list as the second argument to the library function List.iter
. The first argument to List.iter
is a function of type int -> unit
:
let int_list = [ 1 ; 2 ; 3 ]
List.iter (fun i -> print_int i) int_list
Now you need to understand how these expressions in the program were assigned their types. The compiler started at the top of the input file, found the identifier int_list
, and inferred its type from the literal that is bound to it. Next, it found the function List.iter
and knows that its type is ('a -> unit) -> 'a list -> unit
. Since it has a generic or undetermined type 'a
within it, the compiler must first examine the anonymous function (fun i -> print_int i)
. Because the parameter i
is passed to the function print_int
, the compiler infers that the type of i
is int
. Similarly, because the return type of print_int
is unit
, the return type of the anonymous function is unit
. Because the parameter of the anonymous function is an integer, the compiler now knows that the parameter of the type of the undetermined type 'a
is int
, and this means the list passed to the function must be of type int list
.
To fully understand the implications of this, it is helpful to look at an example that does not compile. Consider the next example, where you pass an identifier of type string list
as the second argument to the library function List.iter
whose first argument is a function of type int -> unit
. Then you rewrite the code using the forward operator.
#light
let stringList = [ "1" ; "2" ; "3" ]
List.iter (fun i -> print_int i) stringList
stringList |> List.iter (fun i -> print_int i)
When trying to compile this function, you'll get the following error:
Prog.fs(3,36): error: FS0001: Type mismatch. Expecting a
int list
but given a
string list.
The type int does not match the type string
Prog.fs (4,48): error: FS0001: This expression has type
string
but is here used with type
int
The problem is that in the first case you're expecting an int list
but are given a string list
, and in the second case you're expecting an int
but are given a string
. In the first case, the type of the list was inferred from the type of the function, but in the second case the type of the function was inferred from the type of the list.
That you can infer the type of a function that operates on a list from the type of the list it operates on is incredibly useful when working with .NET libraries not written in F#. Consider the following example, where you define a list of type System.DateTime list
and pass it to List.iter
as the second argument. For the first argument, you try to define a function that will print the Year
property of the DateTime
type. Then you rewrite this function call using the forward operator.
#light
open System
let dateList = [ new DateTime(1999,12,31);
new DateTime(1999,12,31);
new DateTime(1999,12,31) ]
List.iter (fun d -> print_int d.Year) dateList
dateList |> List.iter (fun d -> print_int d.Year)
When trying to compile this function, you'll get the following error:
List.iter (fun d -> print_int d.Year) date_list
------------------------------^^^^^^^
Prog.fs (11,33): error: Lookup on object of indeterminate type. A type annotation
may be needed to constrain the type of the object in order for the lookup to be
resolved.
Note that you receive only one error, when the List.iter
function is written without the forward operator. Written with the forward operator, it compiles successfully. The error you receive means that the compiler has not been able to work out the type of the parameter d
, so it cannot verify that it has a property Year
. In the second case, by using the forward operator, the compiler is able to infer the type of the function is DateTime -> unit
because it has already seen the list dateList
and knows it is of type System.DateTime list
.
One subtlety to watch out for is that, when working with types defined in F#, the compiler can often infer the type from the way it is used. Consider the next example, where you rewrite the previous example, this time using a date
type that you've defined as an F# type, rather than a System.DateTime
, which is defined in a library not written in F#.
#light
type date = { year : int; month : int; day : int }
let fsDateList =
[ { year = 1999 ; month = 12; day = 31 };
{ year = 1999 ; month = 12; day = 31 };
{ year = 1999 ; month = 12; day = 31 } ]
List.iter (fun d -> print_int d.year) fsDateList
fsDateList |> List.iter (fun d -> print_int d.year)
Although this code is virtually the same as the previous example, it will compile without error. This is because the compiler can infer that the parameter d
is of type date
from the use of date
's year
field. It is important to remember that both cases work here, so although the forward operator isn't quite as useful when working with F#'s record types and its union type, you can still use it. In fact, you should consider using it even when working with F# types, because this will lead to working with both external .NET types and F# types in a consistent manner.
The forward operator > operator > operator is also incredibly useful when trying to chain functions together, that is, when one function operates on the result of another. Consider the next example, where you obtain a list of all the .NET assemblies in memory and then process this list until you end up with a list of all the .NET methods in memory. As each function operates on the result of the previous function, the forward operator is used to show the results being piped or passed forward to the next function. You don't need to declare intermediate variables to hold the results of a function.
#light
let methods = System.AppDomain.CurrentDomain.GetAssemblies()
|> List.of_array
|> List.map ( fun assm -> assm.GetTypes() )
|> Array.concat
|> List.of_array
|> List.map ( fun t -> t.GetMethods() )
|> Array.concat
print_any methods
You'll use this technique throughout the rest of the book, particularly when you cover data access in Chapter 9.
In this chapter, you learned about the imperative features of F#. Combined with the functional features in Chapter 3, you now have a full range of techniques to attack any computing problem. F# allows you to choose techniques from the appropriate paradigm and combine them whenever necessary. In the next chapter, you'll see how F# supports the third programming paradigm, object-oriented programming.