Tải bản đầy đủ (.pdf) (32 trang)

Foundations of F#.Net phần 4 pptx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (291.74 KB, 32 trang )

#light
open System.Windows.Forms
let showForm (form : Form) =
form.Show()
// PrintPreviewDialog is defined in the BCL and is
// derived directly the Form class
let myForm = new PrintPreviewDialog()
showForm myForm
When you try to compile the previous example, you will receive the following error:
Prog.fs(11,10): error: FS0001: This expression has type
PrintPreviewDialog
but is here used with type
Form
One way to call a function with a rigid type annotation on a parameter is to use an explicit
upcast at the place where the function is called in order to change the type to be the same as
the type of the function’s parameter. The following line of code changes the type of
myForm to
be the same as the type of the parameter of
showForm:
showForm (myForm :> Form)
Although upcasting the argument to showForm is a solution, it’s not a very pretty one,
because it means littering client code with upcasts. So, F# provides another type annotation,
the derived type annotation, in which the type name is prefixed with a hash sign. This has the
effect of constraining an identifier to be of a type or any of its derived types. This means you
can rewrite the previous example as shown next to remove the need for explicit upcasts in
calling code. I think this is a huge benefit to anyone using the functions you define.
#light
let showFormRevised (form : #Form) =
form.Show()
// ThreadExceptionDialog is define in the BCL and is
// directly derived type of the Form class


let anotherForm = new ThreadExceptionDialog(new Exception())
showFormRevised anotherForm
Y
ou can use this kind of type annotation to tidy up code that uses a lot of casting. F
or
example, as shown in the “Casting” section earlier in this chapter, a lot of casting is often
needed when cr
eating a collection with a common base type
, and this can leave code looking
a little bulkier than it should. A good way to r
emo
v
e this r
epeated casting, as with any com
-
monly r
epeated section of code
, is to define a function that does it for y
ou:
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
84
7575Ch05.qxp 4/27/07 1:02 PM Page 84
#light
let myControls =
[| (new Button() :> Control);
(new TextBox() :> Control);
(new Label() :> Control) |]
let uc (c : #Control) = c :> Control
let myConciseControls =
[| uc (new Button()); uc (new TextBox()); uc (new Label()) |]

This example shows two arrays of controls being defined. The first, myControls, explicitly
upcasts every control; the second,
myConciseControls, delegates this job to a function. Also,
given that the bigger the array, the bigger the savings and that it is quite common for these
arrays to get quite big when working with WinForms, this is a good technique to adopt.
Records As Objects
It is possible to use the record types you met in Chapter 3 to simulate object-like behavior.
This is because records can have fields that are functions, which you can use to simulate an
object’s methods. This technique was first invented before functional programming languages
had object-oriented constructs as a way of performing tasks that lent themselves well to
object-oriented programming. Some programmers still prefer it, because only the function’s
type (or as some prefer, its signature) is given in the record definition, so the implementation
can easily be swapped without having to define a derived class as you would in object-
oriented programming. I discuss this in greater detail in “Object Expressions” and again in
“Inheritance” later in this chapter.
Let’s take a look at a simple example of records as objects. The next example defines a
type,
Shape, that has two members. The first member, reposition, is a function type that
moves the shape, and the second member,
draw, draws the shape. You use the function
makeShape to create a new instance of the shape type. The makeShape function implements
the reposition functionality for you; it does this by accepting the
initPos parameter, which is
then stored in a mutable
ref cell, which is updated when the reposition function is called.
This means the position of the shape is encapsulated, accessible only through the reposition
member
. Hiding values in this way is a common technique in F# programming.
#light
open System.Drawing

type Shape =
{ reposition: Point -> unit;
draw : unit -> unit }
let makeShape initPos draw =
let currPos = ref initPos in
{ reposition = (fun newPos -> currPos := newPos);
draw = (fun () -> draw !currPos); }
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
85
7575Ch05.qxp 4/27/07 1:02 PM Page 85
let circle initPos =
makeShape initPos (fun pos ->
printfn
"Circle, with x = %i and y = %i"
pos.X
pos.Y)
let square initPos =
makeShape initPos (fun pos ->
printfn
"Square, with x = %i and y = %i"
pos.X
pos.Y)
let point (x,y) = new Point(x,y)
let shapes =
[ circle (point (10,10));
square (point (30,30)) ]
let moveShapes() =
shapes |> List.iter (fun s -> s.draw())
let main() =
moveShapes()

shapes |> List.iter (fun s -> s.reposition (point (40,40)))
moveShapes()
main()
Circle, with x = 10 and y = 10
Square, with x = 30 and y = 30
Circle, with x = 40 and y = 40
Square, with x = 40 and y = 40
This example may have seemed trivial, but you can actually go quite a long way with this
technique
. The next example takes things to their natural conclusion, actually drawing the
shapes on a form:
#light
open System
open System.Drawing
open System.Windows.Forms
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
86
7575Ch05.qxp 4/27/07 1:02 PM Page 86
type Shape =
{ reposition: Point -> unit;
draw : Graphics -> unit }
let movingShape initPos draw =
let currPos = ref initPos in
{ reposition = (fun newPos -> currPos := newPos);
draw = (fun g -> draw !currPos g); }
let movingCircle initPos diam =
movingShape initPos (fun pos g ->
g.DrawEllipse(Pens.Blue,pos.X,pos.Y,diam,diam))
let movingSquare initPos size =
movingShape initPos (fun pos g ->

g.DrawRectangle(Pens.Blue,pos.X,pos.Y,size,size) )
let fixedShape draw =
{ reposition = (fun newPos -> ());
draw = (fun g -> draw g); }
let fixedCircle (pos:Point) (diam:int) =
fixedShape (fun g -> g.DrawEllipse(Pens.Blue,pos.X,pos.Y,diam,diam))
let fixedSquare (pos:Point) (size:int) =
fixedShape (fun g -> g.DrawRectangle(Pens.Blue,pos.X,pos.Y,size,size))
let point (x,y) = new Point(x,y)
let shapes =
[ movingCircle (point (10,10)) 20;
movingSquare (point (30,30)) 20;
fixedCircle (point (20,20)) 20;
fixedSquare (point (40,40)) 20; ]
let mainForm =
let form = new Form()
let rand = new Random()
form.Paint.Add(fun e ->
shapes |> List.iter (fun s ->
s.draw e.Graphics)
)
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
87
7575Ch05.qxp 4/27/07 1:02 PM Page 87
form.Click.Add(fun e ->
shapes |> List.iter (fun s ->
s.reposition(new Point(rand.Next(form.Width),
rand.Next(form.Height)));
form.Invalidate())
)

form
[<STAThread>]
do Application.Run(mainForm)
Again, you define a Shape record type that has the members reposition and draw. Then
you define the functions
makeCircle and makeSquare to create different kinds of shapes and
use them to define a list of
shape records. Finally, you define the form that will hold your
records. Here you must do a bit more work than perhaps you would like. Since you don’t use
inheritance, the BCL’s
System.Winows.Forms.Form doesn’t know anything about your shape
“objects,” and you must iterate though the list, explicitly drawing each shape. This is actually
quite simple to do and takes just three lines of code where you add an event handler to
mainForm’s Paint event:
temp.Paint.Add(
fun e ->
List.iter (fun s -> s.draw e.Graphics) shapes);
This example shows how you can quickly create multifunctional records without having
to worry about any unwanted features you might also be inheriting. In the next section, you’ll
look at how you can represent operations on these objects in a more natural way: by adding
members to F# types.
F# Types with Members
It is possible to add functions to both F#’s record and union types. A function added to a record
or union type can be called using dot notation, just like a member of a class from a library not
written in F#. This provides a convenient way of working with records with mutable state. It is
also useful when it comes to exposing types you define in F# to other .NET languages
. (I discuss
this in more detail in Chapter 13.) Some programmers from object-oriented backgrounds just
prefer to see function calls made on an instance value, and this provides a nice way of doing it
for all F# types

.
The syntax for defining an F# record or union type with members is the same as the syn-
tax you learned in Chapter 3, except it includes member definitions that always come at the
end, betw
een the with and end keywor
ds. The definition of the members themselves start with
the keyword
member, followed by an identifier that represents the parameter of the type the
member is being attached to, then a dot, then the function name, and then any other parame-
ters the function takes. After this comes an equals sign followed by the function definition,
which can be any F# expression.
The following example defines a record type,
point. It has two fields, left and top, and a
member function,
Swap. The function Swap is a simple function that swaps the values of left
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
88
7575Ch05.qxp 4/27/07 1:02 PM Page 88
and top. Note how the x parameter, given before the function name swap, is used within the
function definition to get access to the record’s other members, its fields:
#light
type Point =
{
mutable top : int ;
mutable left : int }
with
member x.Swap() =
let temp = x.top
x.top <- x.left
x.left <- temp

end
let printAnyNewline x =
print_any x
print_newline()
let main() =
printAnyNewline myPoint
myPoint.Swap()
printAnyNewline myPoint
main()
The results of this example, when compiled and executed, are as follows:
{top = 3;
left = 7;}
{top = 7;
left = 3;}
You may have noticed the x parameter in the definition of the function Swap:
member x.Swap() =
let temp = x.top
x.top <- x.left
x.left <- temp
This is
the parameter that represents the object on which the function is being called.
When a function is called on a value, as follows:
myPoint.Swap()
the value it is being called on is passed to the function as an argument. This is logical, when
you think about it, because the function needs to be able to access the fields and methods of
the value on which it is being called. Some OO languages use a specific keyword for this, such
as
this or Me, but F# lets you choose the name of this parameter by specifying a name for it
after the keywor
d member

, in this case
x.
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
89
7575Ch05.qxp 4/27/07 1:02 PM Page 89
Union types can have member functions too. You define them in the same way as for record
t
ypes. The next example shows a union type,
D
rinkAmount
,
that has a function added to it:
#light
type DrinkAmount =
| Coffee of int
| Tea of int
| Water of int
with
override x.ToString() =
match x with
| Coffee x -> Printf.sprintf "Coffee: %i" x
| Tea x -> Printf.sprintf "Tea: %i" x
| Water x -> Printf.sprintf "Water: %i" x
end
let t = Tea 2
print_endline (t.ToString())
The results of this example, when compiled and executed, are as follows:
Tea: 2
Note how this uses the keyword override in place of the keyword member. This has the
effect of replacing, or overriding, an existing function of the type. This is not that common a

practice with function members associated with F# types because only four methods are
available to be overridden (
ToString, Equals, GetHashCode, and Finalize) that are inherited
from
System.Object by every .NET type. Because of the way some of these methods interact
with the CLR, the only one I recommend overriding is
ToString. Only four methods are avail-
able for overriding because record and union types can’t act as base or derived classes, so you
cannot inherit methods to override (except from
System.Object).
Object Expressions
Object expressions are at the heart of succinct object-oriented programming in F#. They pro-
vide a concise syntax to create an object that inherits from an existing type. This is useful if
y
ou want to pro
vide a short implementation of an abstract class or an interface or want to
tweak an existing class definition. An object expression allows you to provide an implementa-
tion of a class or interface while at the same time creating a new instance of it.
The syntax is similar to the alter
ative syntax for cr
eating new instances of record types,
with a few small alterations. You surround the definition of an object expression with braces.
At the beginning is the name of the class or interfaces, and the name of a class must be fol-
lowed b
y a pair of parentheses that can have any values passed to the constructor between
them. Interface names need nothing after them, though both class names and interface
names can have a type parameter following them, which must be surrounded by angled
brackets
.
This is followed by the keyword

with and the definition of the methods of the class or
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
90
7575Ch05.qxp 4/27/07 1:02 PM Page 90
interfaces being implemented. These methods are separated by the keyword and, the name of
the method must be the same as the name of a virtual or abstract method in the class or inter-
face definition, and their parameters must be surrounded by parentheses and separated by
commas, like .NET methods must be (unless the method has one parameter, when you can
get away with excluding the parentheses). Ordinarily you don’t need to give type annotations,
but if the base class contains several overall for a method, then you might have to give type
annotations. After the name of a method and its parameters comes an equals sign and then
the implementation of the methods body, which is just an F# expression that must match the
return value of the method.
#light
open System
open System.Collections.Generic
let comparer =
{ new IComparer<string>
with
Compare(s1, s2) =
let rev (s : String) =
new String(Array.rev (s.ToCharArray()))
let reversed = rev s1
reversed.CompareTo(rev s2) }
let winners =
[| "Sandie Shaw" ;
"Bucks Fizz" ;
"Dana International" ;
"Abba";
"Lordi" |]

print_any winners
print_newline()
Array.Sort(winners, comparer)
print_any winners
The r
esults of the previous example, when compiled and executed, are as follows:
[|"Sandie Shaw"; "Bucks Fizz"; "Dana International"; "Abba"; "Lordi"|]
[|"Abba"; "Lordi"; "Dana International"; "Sandie Shaw"; "Bucks Fizz"|]
The previous
shows an example of the
IComparer interface being implemented.
This is an
inter
face with one method,
Compare, which takes two par
ameters and r
eturns an integer that
represents the result of the parameter comparison. It accepts one type parameter; in this case,
y
ou pass it a
string.
Y
ou can see this on the second line of the definition of the identifier
compare
r
.
After this comes the definition of the method body
, which in this case compar
es r
ev

ersed v
ersions
of the str
ing par
ameters
. F
inally
, y
ou use the compar
er b
y defining an arr
ay and then sorting
using the compar
er and displaying the “before” and “after” results in the console.
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
91
7575Ch05.qxp 4/27/07 1:02 PM Page 91
It is possible to implement multiple interfaces or a class and several other interfaces
w
ithin one object expression. It is not possible to implement more than one class within an
object expression. If you are implementing a class and an interface, the class must always
come first in the expression. In either case, the implementation of any other interfaces after
the first interface or class must come after the definitions of all the methods of the first inter-
face or class. The name of the interface is prefixed by the keyword
interface and is followed
by the keyword
with. The definition of the methods is the same as for the first interface or
class.
#light
open System

open System.Drawing
open System.Windows.Forms
let makeNumberControl (n : int) =
{ new Control(Tag = n, Width = 32, Height = 16) with
override x.OnPaint(e) =
let font = new Font(FontFamily.Families.[1], 12.0F)
e.Graphics.DrawString(n.ToString(),
font,
Brushes.Black,
new PointF(0.0F, 0.0F))
interface IComparable with
CompareTo(other) =
let otherControl = other :?> Control in
let n1 = otherControl.Tag :?> int in
n.CompareTo(n1) }
let numbers =
let temp = new ResizeArray<Control>()
let rand = new Random()
for index = 1 to 10 do
temp.Add(makeNumberControl (rand.Next(100)))
temp.Sort()
let height = ref 0
temp |> IEnumerable.iter
(fun c ->
c.Top <- !height
height := c.Height + !height)
temp.ToArray()
let numbersForm =
let temp = new Form() in
temp.Controls.AddRange(numbers);

temp
[<STAThread>]
do Application.Run(numbersForm)
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
92
7575Ch05.qxp 4/27/07 1:02 PM Page 92
The previous example shows the definition of object expression that implements both the
c
lass
C
ontrol
a
nd the interface
I
Comparable
. I
Comparable
a
llows objects that implement this
interface to be compared, primarily so they can be sorted. In this case, the implementation of
IComparable’s CompareTo method sorts the controls according to which number they are dis-
playing. After the implementation of the
makeNumberControl function, you create an array of
controls,
numbers. The definition of numbers is a little complicated; first you initialize it to be
full of controls in a random order, and then you sort the array. Finally, you ensure each control
is displayed at the appropriate height.
Object expressions are a powerful mechanism to quickly and concisely introduce object-
oriented functionality from objects from non-F# libraries into your F# code. They have the
drawback that they do not allow you to add extra properties or methods to these objects. For

example, in the previous example, notice how it was necessary to place the number associated
with control in the control’s
Tag property. This is more of a workaround than a proper solution.
However, sometimes you don’t need extra properties or methods on a type, and this syntax
can be very useful then.
Defining Interfaces
Interfaces can contain only abstract methods and properties. They define a “contract” for all
classes that implement them, exposing those components that clients can use while insulat-
ing clients from their actual implementation. A class can inherit from only one base class, but
it can implement any number of interfaces. Since any class implementing an interface can be
treated as being of the interface type, interfaces provide similar benefits but avoid the com-
plexities of multiple-class inheritance.
You define interfaces using the keyword
interface; after this, you list all the members of
the interface. The types of members that interfaces can have are somewhat limited, interfaces
have no constructors, and they can declare only abstract methods and properties.
The following code defines an interface that declares one method,
ChangeState.
type MyInterface = interface
abstract ChangeState : myInt : int -> unit
end
Implementing Interfaces
T
o implement an inter
face, use the keyword
interface, follo
w
ed b
y the interface name, then
keyword

with, then the code to affect the interface members
, and then the keyword
end. M
em
-
ber definitions are prefixed by the keyword
member but otherwise are the same as the definition
of any method or pr
operty. You can implement interfaces by either classes or structs; I cover
how to cr
eate classes in some detail in the follo
wing sections
, and I co
v
er structs in the section
“Structs” later in this chapter.
The next example defines, implements
, and uses an interface. The class
Implementation
implements the inter
face
MyInterface.
#light
type MyInterface = interface
abstract ChangeState : myInt : int -> unit
end
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
93
7575Ch05.qxp 4/27/07 1:02 PM Page 93
type Implementation = class

val mutable state : int
new() = {state = 0}
interface MyInterface with
member x.ChangeState y = x.state <- y
end
end
let imp = new Implementation()
let inter = imp :> MyInterface
let pintIntNewline i =
print_int i
print_newline()
let main() =
inter.ChangeState 1
pintIntNewline imp.state
inter.ChangeState 2
pintIntNewline imp.state
inter.ChangeState 3
pintIntNewline imp.state
main()
The results are as follows:
1
2
3
Note near the end of the example you must cast the identifier imp to the interface
MyInterface before you can use the method ChangeState:
let imp = new Implementation()
let inter = imp :> MyInterface
This is because inter
faces ar
e explicitly implemented in F#. I

f you want the methods of
the interface to be available directly on the class that implements it, instead of after casting
the object to the interface, you can add the interface members to the definition of the class. To
r
evise the example
, y
ou simply add
ChangeState as a member of the class Implementation.
Now it is no longer necessary to cast the identifier
imp. I cover adding members to methods in
the section “Classes and Methods” later in the chapter.
#light
type MyInterface = interface
abstract ChangeState : int -> unit
end
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
94
7575Ch05.qxp 4/27/07 1:02 PM Page 94
type Implementation = class
val mutable state : int
new() = {state = 0}
interface MyInterface with
member x.ChangeState y = x.state <- y
member x.ChangeState y = x.state <- y
end
let imp = new Implementation()
let pintIntNewline i =
print_int i
print_newline()
let main() =

imp.ChangeState 1
pintIntNewline imp.state
imp.ChangeState 2
pintIntNewline imp.state
main()
The results are as follows:
1
2
Classes, Fields, and Explicit Constructors
Until now you’ve relied on classes available in non-F# libraries. In this and the following sec-
tions, you’ll learn how to define classes of your own. First, you’ll learn how to define fields and
constructors so you can create instances. Then, in the following sections, you’ll learn about
inheritance and methods associated with classes.
In F#, classes are essentially just special kinds of types, so you define them using the
type
keywor
d followed by an equals sign and then the keyword
class.
To terminate your class defi-
nition, you use the keyword
end. The next example shows the simplest class definition
possible, of a class with nothing in it, or an empty class. It then attempts to instantiate it.
#light
type Empty = class
end
let emptyItem = new Empty()
This code will not compile because of the last line. You didn’t provide a constructor for
our class, so there’s no way to create an instance of it. To enable you to create an instance of
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
95

7575Ch05.qxp 4/27/07 1:02 PM Page 95
the class, you must explicitly add a constructor. To do this, you need to add a member, which
i
s always named
n
ew
a
nd is followed the constructor within parentheses. After this comes an
equals sign followed by a block (delimited by braces), which contains expressions to initialize
every field in the class. The following example defines a simple constructor for an empty class:
#light
type JustConstruct = class
new() = {}
end
let constructed = new JustConstruct()
This may come as a surprise to experienced object-oriented programmers since most
object-oriented languages provide a default constructor for any class that doesn’t define one.
F#’s philosophy is to provide a more powerful construct called implicit class construction that
often subsumes the need for explicit constructors altogether. You’ll return to implicit class con-
struction in the next section. Furthermore, default constructors can easily leave some fields
uninitialized and therefore
null and can leave some at risk of causing a NullReferenceException.
This is why a constructor in F# must initialize all fields defined by a class.
Fields are defined using the keyword
val, followed the name of the field, and then the
name of the type separated from the property name by a colon. The next example shows a
simple class,
file1, that has two fields, path and innerFile, that are initialized in the construc-
tor, which has one parameter,
path:

#light
open System.IO
type File1 = class
val path: string
val innerFile: FileInfo
new(path) =
{ path = path ;
innerFile = new FileInfo(path) }
end
let myFile1 = new File1("whatever.txt")
It’s possible to overload constructors; one simply adds a second constructor with a differ-
ent number of parameters. If you want to overload with parameters of different types, then
you must provide type annotations. The following example shows a class,
File2, with two con-
structors, one with no parameters and one with one parameter:
#light
open System.IO
type File2 = class
val path: string
val innerFile: FileInfo
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
96
7575Ch05.qxp 4/27/07 1:02 PM Page 96
new() = new File2("default.txt")
new(path) =
{ path = path ;
innerFile = new FileInfo(path) }
end
let myFile2 = new File2("whatever2.txt")
Note that the only thing you can do in the initialization block of a constructor is to initial-

ize the fields of a class or call another constructor to do that for you. If you want to do other
things in a constructor, you must you use the keyword
then after the block and follow it by the
extra expressions you want in the constructor. This separates the initialization of fields from
other code to ensure that nothing can happen to the fields of a class before they are properly
initialized. If you want to access the fields in a class outside the initialization block, you must
give a name to the instance you’re creating by qualifying the constructor; you do this using the
keyword
as followed by the alias for the instance. The next example shows a constructor with
some code following the initialization block. The alias,
x, is defined for the instance. This is
later used to test whether the file associated with the
FileInfo object bound to the field
innerFile exists.
#light
open System.IO
type File3 = class
val path: string
val innerFile: FileInfo
new(path) as x =
{ path = path ;
innerFile = new FileInfo(path) }
then
if not x.innerFile.Exists then
let textFile = x.innerFile.CreateText()
textFile.Dispose()
end
let myFile3 = new File3("whatever.txt")
By default, fields in a class are immutable, which means once they have been bound to a
value, the value can be rebound to another value. For F# records with mutable fields and .NET

objects, this does not mean their inter
nal state cannot change; it simply means y
ou cannot
replace the whole value to which the field is bound. You can see this in the previous example;
if the file you are creating doesn’t exist, the file will be created, changing the value of the
Exists flag to true. H
o
w
ever, you cannot set the field
innerFile to be another instance of the
FileInfo object.
From time to time, it can be useful to rebind a field to another value. To allow this to hap-
pen, F# pr
o
vides the keywor
d
mutable; when a field is defined as mutable, it can be r
ebound
whenever the programmer chooses. The following example illustrates its usage. In this exam-
ple, you see that the
mutable keyword is applied to the FileInfo field so that you can change
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
97
7575Ch05.qxp 4/27/07 1:02 PM Page 97
the instance of the object it refers to later. You see that if the file does not exist, it is replaced by
t
he first file available in the directory.
#light
open System.IO
type File4 = class

val path: string
val mutable innerFile: FileInfo
new(path) as x =
{ path = path ;
innerFile = new FileInfo(path) }
then
if not x.innerFile.Exists then
let dir = x.innerFile.Directory in
let files = dir.GetFiles() in
if files.Length > 0 then
x.innerFile <- files.(0)
else
failwith "no files exist in that dir"
end
let myFile4 = new File4("whatever2.txt")
Implicit Class Construction
So far you’ve defined classes using the explicit syntax for constructors. A recent enhancement to
F# is called implicit class construction or the compact class syntax. This allows a class to implic-
itly define a construction sequence through a series of
let bindings prior to the member
definitions of the class. These bindings are private to the class. For example, you can define the
File1 example from the previous section simply by using the following:
type File1(path) = class
let innerFile = new FileInfo(path)
member x.InnerFile = innerFile
end
let myFile1 = new File1("whatever.txt")
Classes using implicit class construction have a tuple of arguments such as path after the
name of the type constr
uctor. Furthermore, the body of the class may contain

let bindings
,
which are always pr
iv
ate to the class
. H
ere you have also added the property member
InnerFile to reveal the value of the value of the innerFile. I discuss property members later in
this chapter
. H
ere is a second example:
type Counter(start, increment, length) = class
let finish = start + length
let mutable current = start
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
98
7575Ch05.qxp 4/27/07 1:02 PM Page 98
member obj.Current = current
member obj.Increment() =
if current > finish then failwith "finished!";
current <- current + increment
end
Logically speaking, this class is equivalent to the following one that defines fields and a
constructor explicitly:
// The previous code is equivalent to the following:
type Counter = class
val start: int
val increment: int
val length : int
val finish : int

val mutable current : int
new(start, increment, length) =
{ start=start;
increment=increment;
length=length;
finish = start + length;
current = start; }
member obj.Current = current
member obj.Increment() =
if obj.current > obj.finish then failwith “finished!”;
obj.current <- obj.current + obj.increme
end
However, the F# compiler is free to optimize the fields of the class away where possible.
For example, the
start value is required only during initialization of the object and thus will
not be included as a field of the object.
The first definition of
Counter is a third the size of the second, so obviously the syntax has
some advantages. I use both implicit and explicit constructors in this book because it is neces-
sary in some circumstances to understand the more explicit syntax, for example, when writing
classes with multiple constructors.
Classes and Inheritance
I have already covered inheritance in a limited way in the section “Object Expressions.” Inheri-
tance allows you to extend a class that is already defined and to tweak its behavior to add new
or r
eplace original functionality. Like most modern object-oriented languages, F# allows
single inheritance (from one base class) as well as the implementation of multiple interfaces
(see the sections “Defining interfaces” and “Implementing Interfaces” later in this chapter).
This section will cover the very basics of inheritance, and then the following section, “Classes
and Their Methods,” will show how to implement methods to make full use of inheritance.

You specify inheritance with the
inherit keyword, which must come directly after the
keyword
class. Let’s kick off by looking at a simple example of inheritance between two F#
types. The following example shows an F# class,
sub, that derives from a base class, base. The
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
99
7575Ch05.qxp 4/27/07 1:02 PM Page 99
class base has one field, state, and the class sub has another called otherState. The example
shows that both fields can be used by the
sub derived class, because state is inherited from
the base class.
#light
t
ype Base = class
val state: int
new() = { state = 0 }
end
type Sub = class
inherit Base
val otherState: int
new() = { otherState = 0 }
end
let myObject = new Sub()
printfn
"myObject.state = %i, myObject.otherState = %i"
myObject.state
myObject.otherState
The results of this example, when compiled and executed, are as follows:

myObject.state = 0, myObject.otherState = 0
For this to work properly, the base class must have a parameterless constructor, that is, a
constructor that does not take any arguments. If the base class does not have a parameterless
constructor, then it cannot be initialized implicitly when the derived class is instantiated. This
doesn’t mean you can’t derive from it; it just means you need to explicitly call the base class’s
constructor. You do this using the
inherit keyword again but this time within the constructor’s
initializer block. The following example shows how to derive from a class that has no parame-
terless constructor:
#light
type Base1 = class
val state: int
new(state) = { state = state }
end
type Sub1 = class
inherit Base1
val otherState: int
new(state) = { inherit Base1(state) ; otherState = 0 }
end
let myOtherObject = new Sub1(1)
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
100
7575Ch05.qxp 4/27/07 1:02 PM Page 100
printfn
"myObject.state = %i, myObject.otherState = %i"
myOtherObject.state
myOtherObject.otherState
T
he results of this example, when compiled and executed, are as follows:
myOtherObject.state = 1, myOtherObject.otherState = 0

When using implicit class construction, the call to the base class constructor is specified
as part of the
inherits declaration. For example, you can rewrite the previous example as fol-
lows, giving the same results:
type Base1(state) = class
member x.State = state
end
type Sub1(state) = class
inherit Base1(state)
member x.OtherState = state
end
let myOtherObject = new Sub1(1)
printfn
"myObject.state = %i, myObject.otherState = %i"
myOtherObject.State
myOtherObject.OtherState
Classes and Methods
The previous two sections gave you the basics of putting together a class. Now you’ll take a
look at really getting the most out of object-oriented programming by adding methods to your
class. A method is a function that has access to the fields of an object and can change them if
they ar
e mutable. A derived class can define new methods and can override methods inher-
ited from its base class.
Methods are defined using four keywords, either
member, override, abstract, or default. The
simplest way to declar
e a method is to use the
member keywor
d; this defines a method that can-
not be overridden. The

override keyword defines a method that overrides an inherited method
that has an implementation in a base class. The
abstract keyword defines a method that has no
implementation and must be o
verridden in a derived class. The keyword
default has a similar
meaning to the
override keyword, except it is only ever used to override an abstract method.
The
member, override, and default definitions have the same syntax. The keyword is fol-
lowed by the parameter that represents the instance of the object whose class you are in the
process of defining. You can use this parameter in the method implementation to get access to
all the class’s fields and properties. After this special parameter comes a dot and then the
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
101
7575Ch05.qxp 4/27/07 1:02 PM Page 101
name of the method. Next come the parameters of the method. After that comes an equals
s
ign followed by the implementation of the method.
Methods declared using the keyword
abstract are a little different, because there is no
implementation. Because there is no implementation, you omit the parameter that represents
the object itself, so the keyword
abstract is followed directly by the method name. Following
this is the method’s type, which is separated from the method name using a colon, just like
any other type annotation.
The next example is designed to illustrate the use of all four kinds of methods:
#light
type Base = class
val mutable state: int

new() = { state = 0 }
member x.JiggleState y = x.state <- y
abstract WiggleState: int -> unit
default x.WiggleState y = x.state <- y + x.state
end
type Sub = class
inherit Base
new() = {}
default x.WiggleState y = x.state <- y &&& x.state
end
let myBase = new Base()
let mySub = new Sub()
let testBehavior (c : #Base) =
c.JiggleState 1
print_int c.state
print_newline()
c.WiggleState 3
print_int c.state
print_newline()
print_endline "base class: "
testBehavior myBase
print_endline "sub class: "
testBehavior mySub
The r
esults of this example, when compiled and executed, are as follows:
base class:
1
4
sub class:
1

1
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
102
7575Ch05.qxp 4/27/07 1:02 PM Page 102
You first implement a method, JiggleState, in class Base. The method cannot be overrid-
den, so all derived classes will inherit this implementation. You then define an abstract
method,
WiggleState, that can be overridden (and, in fact, must be) by derived classes. To
define a new method that can be overridden, you always need to use a combination of the
abstract and default keywords. This could mean that abstract is used on the base class while
the default is used on the derived class, but often you will use them together in the same class,
as shown in the previous example. This requires the programmer to explicitly give types to a
method they are providing to be overridden. Although the F# philosophy is generally not to
require the programmer to give explicit types and to try to let the compiler work them out, the
compiler has no way to infer these types, so you must give them explicitly.
As shown in the results when
JiggleState is called, the behavior remains the same in
both the base class and the derived class, where the behavior of
WiggleState changes because
it is overridden.
Accessing the Base Class
When accessing methods within a class, they will usually call the version of the method in the
most derived class. That means if you try to call a method on the base class and it has been
overridden by the derived class, then it will automatically call the version on the derived class.
Ordinarily this is used to call the base implementation of a method you are overriding. This
isn’t always necessary but generally is required by library design guidelines because it can lead
to the base class malfunctioning if you do not do this.
To get access to methods on the base class, you give the base class a name. You do this by
using the keyword
as after the name of the class you are inheriting from, followed by the name

you want to give to the base class. This name then acts like the keyword
base in C#, giving you
access to the base class’s methods.
The following example shows an implementation of a class that derives from
System.
Windows.Form
. The identifier base is assigned to base class Form, as shown at the top of the defi-
nition of the
MySquareForm class. The example uses implicit class construction, indicated by
the fact that the type
MySquareForm takes a parameter, color.
open System.Drawing
open System.Windows.Forms
type MySquareForm(color) = class
inherit Form() as base
override x.OnPaint(e) =
e.Graphics.DrawRectangle(color,
10, 10,
x.Width - 30,
x.Height - 50)
base.OnPaint(e)
override x.OnResize(e) =
x.Invalidate()
base.OnResize(e)
end
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
103
7575Ch05.qxp 4/27/07 1:02 PM Page 103
let form = new MySquareForm(Pens.Blue)
do Application.Run(form)

In this form you override two methods, OnPaint and OnResize, and in these methods you
use the identifier base, which grants access to the base class, to call the base class’s implemen-
t
ation of this method.
Properties and Indexers
Properties are a special form of method that look like a value to the code that calls it. Indexers
are a similar concept that makes a method look a bit like a collection to the calling code. Both
properties and indexers have accessors, which include a get accessor for reading and a set
accessor for writing.
A property definition starts the same way as a method definition, with the keyword
member
followed by the parameter that represents the object, then a dot, and then the member name.
After this, instead of the method parameters, you use the keyword
with, followed by either get
or set. Then comes the parameters; a get method must take unit, and a set method must take
one single parameter. After this is an equals sign and an expression that forms the method
body. If a second method is required, you use the keyword
and to join them together.
The following sample shows the definition of a class that has a single property,
MyProp,
which returns a random number. Setting the property resets the seed of the random number
generator.
#light
type Properties() = class
let mutable rand = new System.Random()
member x.MyProp
with get () = rand.Next()
and set y = rand <- new System.Random(y)
end
let prop = new Properties()

let print i = printfn "%d" i
prop.MyProp <- 12
print prop.MyProp
print prop.MyProp
print prop.MyProp
The results of the previous example, when compiled and executed, are as follows:
2137491492
726598452
334746691
It is also possible to declare abstract properties. The syntax is similar, the keyword member
is replaced by abstract, and the parameter that represents the object is omitted, just like for a
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
104
7575Ch05.qxp 4/27/07 1:02 PM Page 104
method. After the member name comes the name of the type separated from the member
n
ame by a colon. Then follows the keyword, followed by either
g
et
o
r
s
et
,
representing
whether the inheritor must implement a get or set method, or both, separated by a comma.
Properties look exactly like a field to the calling code.
The following example shows the previous example revised so now it uses a base class,
AbstractProperties. You will notice how the derived class ConcreteProperties must imple-
ment the get and set methods using the keywords

with and then and.
#light
type AbstractProperties() = class
abstract MyProp : int
with get, set
end
type ConcreteProperties() = class
inherit AbstractProperties()
let mutable rand = new System.Random()
override x.MyProp
with get() = rand.Next()
and set(y) = rand <- new System.Random(y)
end
Indexers are properties that take two or more parameters, one to represent the element
being placed in the pseudocollection and others to represent the index in it. In C# all indexers
are called
Item in the underlying implementation, but the programmer never actually uses
this name because it is always implicit. In F#, the programmer can choose the name of the
indexer property. If the programmer chooses the name
Item, then there is special syntax for
accessing the property.
The syntax for creating an indexer is the same as a property, except a get method has one
or more parameters, and a set method has two or more parameters. To access an element in
an indexer, if its name is
Item, you can use a special syntax that looks like array access except
with the parentheses replaced by square brackets:
#light
type Indexers(vals:string[]) = class
member x.Item
with get (y) = vals.[y]

and set (y, z) = vals.[y] <- z
member x.MyString
with get (y) = vals.[y]
and set (y, z) = vals.[y] <- z
end
let index = new
Indexers [|"One"; "Two"; "Three"; "Four"|]
index.[0] <- "Five";
index.Item(2) <- "Six";
index.MyString(3) <- "Seven";
print_endline index.[0]
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
105
7575Ch05.qxp 4/27/07 1:02 PM Page 105
print_endline (index.Item(1))
print_endline (index.MyString(2))
print_endline (index.MyString(3))
The results of the previous example, when compiled and executed, are as follows:
Five
Two
Six
Seven
■Note When working with indexers with a name other than Item, remember that it will be difficult for
other .NET languages to use your classes.
Classes and Static Methods
Static methods are like instance methods, except they are not specific to any instance of a
class so have no access to the class’s fields.
To create a static method, you use the keyword
static, followed by the keyword member.
Then comes the method name, its parameters, an equals sign, and then the method defini-

tion. This is basically the same as declaring an instance method, just with the addition of the
keyword static and the removal of the parameter that represents the object. Removing the
parameter that represents the object is quite logical because the method has no access to the
object’s properties.
The following example shows the definition of a static method,
rev_string, associated
with a class,
MyClass:
#light
type MyClass = class
static member revString (s : string) =
let chars = s.ToCharArray() in
let reved_chars = Array.rev chars in
new string(reved_chars)
end
let myString = MyClass.revString "dlrow olleH"
print_string myString
The results of the previous example, when compiled and executed, are as follows:
Hello world
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
106
7575Ch05.qxp 4/27/07 1:02 PM Page 106
You will notice from the previous example that the static methods called use the name of the
t
ype they are associated with, rather than a value of the type with which the method is associated.
Static methods can also be useful for providing operators for your classes to use. The
basic syntax for declaring an operator is the same as for declaring any other static method,
except the name of the method is replaced by the operator in brackets. The parameters of the
operator must be given as a tuple and typically need type annotations to indicate their types.
The following example assumes that for some reason you want to reimplement the

int
type in a class called MyInt. The MyInt class has a plus operator defined on it.
#light
type MyInt(state:int) = class
member x.State = state
static member ( + ) (x:MyInt, y:MyInt) : MyInt = new MyInt(x.State + y.State)
override x.ToString() = string_of_int state
end
let x = new MyInt(1)
let y = new MyInt(1)
printfn "(x + y) = %A" (x + y)
The results of the previous example, when compiled and executed, are as follows:
(x + y) = 2
Overriding Methods from Non-F# Libraries
When overriding methods from non-F# libraries, the method definition must be in the tuple
style, that is, surrounded by brackets and separated by commas. If you need to use a method
like this as a value, then you will need to create an F# function from the method.
The follo
wing sample shows a class that implements the interface
System.Net.ICredentials. Its single method, GetCredential, has two parameters. Just after the
interface has been implemented, the example demonstrates using it as a value in the method
GetCredentialList.
#light
type CredentialsFactory() = class
interface System.Net.ICredentials with
member x.GetCredential(uri, authType) =
new System.Net.NetworkCredential("rob", "whatever", "F# credentials")
member x.GetCredentialList uri authTypes =
let y = (x :> System.Net.ICredentials)
let getCredential s = y.GetCredential(uri, s)

List.map getCredential authTypes
end
I discuss the relationship between F# signatures and C# signatures in Chapter 13.
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
107
7575Ch05.qxp 4/27/07 1:02 PM Page 107
Defining Delegates
D
elegates are the mechanism both C# and Visual Basic use to treat their methods as values.
A delegate basically acts as a .NET object that wraps the method and provides an invoke
method so it can be called. There is rarely a need to define delegates in F# because it can treat
a
function as a value without the need for any wrapper. However, sometimes they are useful—
to define delegates to expose F# functionality to other .NET languages in a friendlier manner
and to define callbacks for directly calling C code from F#.
To define a delegate, you use the keyword
delegate followed directly by the keyword of
and then the type of the delegate’s signature, which follows the standard F# type annotation
notation.
The next example shows the definition of a delegate,
MyDelegate, which takes an int and
returns
unit. You then create a new instance of this delegate and apply it to a list of integers.
As you’ve already seen in Chapter 3, there are much shorter ways of implementing this func-
tionality in F#.
#light
type MyDelegate = delegate of int -> unit
let inst = new MyDelegate (fun i -> print_int i)
let ints = [1 ; 2 ; 3 ]
ints

|> List.iter (fun i -> inst.Invoke(i))
The results of this example, when compiled and executed, are as follows:
123
Structs
Y
ou define structs in a similar manner to
classes.
The keyword
class is r
eplaced with
struct.
The main difference between a class and struct is the area of memory where the object will be
allocated. When used as a local variable or parameter, a struct is allocated on the stack, while a
class is allocated on the managed heap
. B
ecause str
ucts are allocated on the stack, they are
not garbage collected but are automatically deallocated when a function exits. It is generally
slightly faster accessing their fields and slightly slower passing them to methods, but these dif-
fer
ences do tend to be quite small. Because they ar
e allocated on the stack, it is generally best
to create structs with a small number of fields to avoid stack overflow. You can’t use inheri-
tance when implementing structs, so this means structs can’t define virtual methods or
abstr
act methods
.
The next
example defines a str
uct r

epr
esenting an IP addr
ess
. N
ote the only difference
from defining a class is that the keyword
struct is used.
CHAPTER 5 ■ OBJECT-ORIENTED PROGRAMMING
108
7575Ch05.qxp 4/27/07 1:02 PM Page 108

×