The unit Type 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 funct
Trang 1Imperative Programming
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 enablessome 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,
muta-ble 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
mem-bers such as indexers and events, and using the F# |> operator, which is handy when dealing
with NET libraries
The unit Type
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:
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
nonfunc-tion value, the expressions within it would be evaluated only once Since it is a funcnonfunc-tion, the
expressions will be evaluated each time it is called
55
C H A P T E R 4
n n n
Trang 2n Caution Just because a function is named maindoesn’t mean it is the entry point of the program and isexecuted 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 returnnothing Ordinarily, you need to put something between the equals sign and the empty paren-theses, or the function is pointless; however, for the sake of keeping things simple, I’ll leavethis 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 unitand 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 itwith some special imperative constructs To call the function, you can use the let keyword fol-lowed 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 callthe function without any extra keywords at all:
#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 somethingmost programmers want to avoid So, to avoid this, it’s sometimes useful to turn a functionthat does return a value into a function of type unit, typically because it has a side effect Theneed to do this is fairly rare when just using F# libraries written in F# (although situationswhere it is useful do exist), but it is more common when using NET libraries that were notwritten in F#
Trang 3The next example shows how to turn a function that returns a value into a function thatreturns unit:
what-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
demon-strated 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
The mutable Keyword
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
dis-cussed in the previous section The next example demonstrates defining a mutable identifier of
type string and then changing the changing the value it holds:
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
pro-duced if you try to change the type; the next example demonstrates this:
Trang 4let 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
intbut is here used with type
string
The other major difference is where these changes are visible When you redefine an tifier, the change is visible only within the scope of the new identifier When it passes out ofscope, it reverts to its old value This is not the case with mutable identifiers Any changes arepermanent, whatever the scope The next example demonstrates this:
printfn "x = %s" xelse ()
printfn "x = %s" xlet mutableX() =
let mutable x = "One"
printfn "Mutating:\r\nx = %s" x
if true then
x <- "Two"
printfn "x = %s" xelse ()
printfn "x = %s" xredefineX()
Trang 5Identifiers defined as mutable are somewhat limited because they can’t be used within asubfunction You can see this in the next example:
#light
let mutableY() =
let mutable y = "One"
printfn "Mutating:\r\nx = %s" ylet f() =
y <- "Two"
printfn "x = %s" yf()
printfn "x = %s" yThe 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, hasbeen 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
Defining Mutable Record Types
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\r\n" o
Trang 6theCouple.him <- "Richard Burton";
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 fieldhim is mutable but the field her is not Next, an instance of couple is initialized, and then youchange the value of him many times, each time displaying the results I should note that themutable keyword applies per field, so any attempt to update a field that is not mutable willresult in a compile error; for example, the next example will fail on the second line:
The ref Type
The ref type is a simple way for a program to use mutable state, that is, values that changeover 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 straightfor-
ward 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
Trang 7Creating a new instance of the ref type is easy; you use the keyword ref followed by ever item represents the value of ref The next example is the compiler’s output (using the –i
what-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 previouschapter The ref type has two built-in operators to access it; the exclamation point (!) pro-
vides 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 thethird 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 0for n in a do
x := !x + nprint_int !xprint_newline()totalArray()
The result is as follows:
6
n 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 reftype’s !operator as a Boolean
“not” operator F# uses a function called notfor Boolean “not” operations
Trang 8The ref type is a useful way to share mutable values between several functions An fier can be bound to a ref type defined in scope that is common to all functions that want touse the value; then the functions can use the value of the identifier as they like, changing it ormerely reading it Because in F# functions can be passed around as if they were values, every-
identi-where 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, andshow are all defined in their own private scopes and then returned to the top level as a tuple sothey are visible everywhere Note how n is not returned; it remains private, but inc, dec, andshow are all still able to access n This is a useful technique for controlling what operations cantake place on mutable data
#light
let inc, dec, show =
let n = ref 0let inc () =
n := !n + 1let dec () =
n := !n - 1let show () =print_int !ninc, dec, showinc()
Arrays are a mutable collection type in F# Arrays are the opposite of lists, discussed inChapter 3 The values within arrays are updatable, whereas lists are not, and lists can growdynamically, 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 bracketand a vertical bar ([|) and a closing bar and square bracket (|]) The syntax for referencing anarray element is the name of the identifier of the array followed by period (.) and then theindex of the element in square brackets ([]) The syntax for retrieving the value of an element
Trang 9stops 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, ray, is defined, and then you read all the members from it Then you insert new values into the
rhymeAr-array, and finally you print out all the values you have
let firstPiggy = rhymeArray.[0]
let secondPiggy = rhymeArray.[1]
let thirdPiggy = rhymeArray.[2]
let fourthPiggy = rhymeArray.[3]
[|"Wee,"; "wee,"; "wee,"; "all the way home"|]
Arrays, like lists, use type parameterization, so the type of the array is the type of its tents followed by the array’s type, so rhymeArray has type string array, which may also be
con-written string[]
Multidimensional arrays in F# come in two slightly different flavors, jagged and
rectangu-lar 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
Trang 10For 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 innerarray and the second time you get the element within it
The next example demonstrates a simple jagged array, called jagged The array membersare accessed in two different ways The first inner array (at index 0) is assigned to the identifiersingleDim, and then its first element is assigned to itemOne On the fourth line, the first ele-ment 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 insquare brackets, separated by commas Unlike jagged arrays, which are multidimensional butcan be defined using the same ([||]) syntax as single-dimensional arrays, you must createrectangular arrays with the create function of the Array2 and Array3 modules, which supporttwo- and three-dimensional arrays, respectively This doesn’t mean rectangular arrays are lim-ited 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 beconsidered carefully, because adding extra dimensions can quickly lead to very large objects.The next example creates a rectangular array, square Then its elements are populatedwith the integers 1, 2, 3, and 4
printf "%A\r\n" square
Now let’s look at the differences between jagged and rectangular arrays First create ajagged array to represent Pascal’s Triangle:
Trang 11let triangularIndex = index - 2 in
if triangularIndex >= 0 thentemp.[1, index] <- pascalsTriangle.[index].[triangularIndex];
let tetrahedralIndex = index - 3 in
if tetrahedralIndex >= 0 thentemp.[2, index] <- pascalsTriangle.[index].[tetrahedralIndex]
donetempThen display the sequences you’ve retrieved:
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 ajagged 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[,]
Trang 12n Caution To write code that is compatible with both NET 1.1 and 2.0, you must use the Microsoft.
differ-ences 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 reallygeneric, since NET 1.1 doesn’t support generics In NET 2.0, arrays became properly generic, and theirbehavior changed in a number of subtle ways that F# cannot abstract away The result is that you mustexplicitly choose whether you want to use arrays that are supported in NET 1.1
Array Comprehensions
I introduced compression syntax in Chapter 3 for lists and sequences You can use a ding syntax to create arrays The only difference between this and the functional-style syntax isthe characters that delimit the array You use vertical bars surrounded by square brackets forarrays:
correspon-#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:
The major difference from using the if expression in the imperative style, that is, using itwith a function that returns type unit, is that you aren’t forced to use an else, as the nextexample demonstrates:
#light
if System.DateTime.Now.DayOfWeek = System.DayOfWeek.Sunday then
print_endline "Sunday Playlist: Lazy On A Sunday Afternoon - Queen"
Trang 13Though 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 tothe 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 -
Beat-les" 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 simplygive 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 inimperative 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
fol-lowed by the identifier that will hold the counter value; then comes an equals sign, folfol-lowed 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 ues in an array The identifier index will take on values starting at 0 and ending at 1 less than
val-the length of val-the array You can use this identifier as val-the index for val-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]