CHAPTER 5

Object-Oriented Programming

Object-oriented programming is the third major programming paradigm. At its heart, object-oriented programming has a few simple ideas, some of which you've already encountered. Possibly the most important idea is that the implementations and state should be encapsulated, that is, hidden behind well-defined boundaries. This makes the structure of a program easier to manage. In F#, things are hidden by using signatures for modules and type definitions and also by simply defining them locally to an expression or class construction (you'll see examples of both in this chapter).

The second idea is that you can implement abstract entities in multiple ways. In OOP this is known as polymorphism. You've met a number of simple abstract entities already, such as function types. A function type is abstract because a function with specific type can be implemented in many different ways; for example, the function type int -> int can be implemented as a function that increments the given parameter, a function that decrements the parameter, or any one of millions of mathematical sequences. Other abstract entities can be built out of existing abstract components such as the interface types defined in the .NET BCL. More sophisticated abstract entities are modeled using user-defined interface types. Interface types have the advantage that they can be arranged hierarchically; this is called interface inheritance. For example, the .NET BCL includes a hierarchical classification of collection types, available in the System.Collections and System.Collections.Generic namespaces.

In OOP you can sometimes arrange implementation fragments hierarchically. This is called implementation inheritance. This tends to be less important in F# programming because of the flexibility that functional programming provides for defining and sharing implementation fragments. However, it is significant for domains such as graphical user interface (GUI) programming.

Casting

Casting is a way of explicitly altering the static type of a value by either throwing information away, upcasting, or rediscovering it, downcasting. In F#, upcasts and downcasts have their own operators. The type hierarchy starts with obj (or System.Object) at the top and all its descendants below it. An upcast moves a type up the hierarchy, and a downcast moves a type down the hierarchy.

Upcasts change a value's static type to one of its ancestor types. This is a safe operation since the compiler can always tell whether this will work because the compiler always knows all the ancestors of a type so is able to work out through static analysis whether an upcast will be successful. An upcast is represented by a colon followed by the greater-than sign (:>). The following code shows an example of using an upcast to convert a string to an obj:

#light
let myObject = ("This is a string" :> obj)

Generally, upcasts are required when defining collections that contain disparate types. If an upcast is not used, the compiler will infer that the collection has the type of the first element and give a compile error if elements of other types are placed in the collection. The next example demonstrates how to create an array of controls, a pretty common task when working with WinForms. Notice that all the individual controls are upcast to their common base class, Control.

#light
open System.Windows.Forms

let myControls  =
    [| (new Button() :> Control);
       (new TextBox() :> Control);
       (new Label() :> Control) |]

An upcast also has the effect of automatically boxing any value type. Value types are held in memory on the program stack, rather than on the managed heap. Boxing means that the value is pushed onto the managed heap, so it can be passed around by reference. The following example demonstrates a value being boxed:

#light
let boxedInt = (1 :> obj)

A downcast changes a value's static type to one of its descendant types and thus recovers information hidden by an upcast. Downcasting is dangerous since the compiler doesn't have any way to statically determine whether an instance of a type is compatible with one of its derived types. This means you can get it wrong, and this will cause an invalid cast exception (System.InvalidCastException) to be issued at runtime. Because of the inherent danger of downcasting, it is often preferred to replace it with pattern matching over .NET types, as demonstrated in Chapter 3. Nevertheless, a downcast can be useful in some places, so a downcast operator, composed of a colon, question mark, and greater-than sign (:?>), is available. The next example demonstrates downcasting:

#light
open System.Windows.Forms

let moreControls  =
    [| (new Button() :> Control);
    (new TextBox() :> Control) |]

let control =
    let temp = moreControls.[0]
    temp.Text <- "Click Me!"
    temp

let button =
    let temp = (control :?> Button)
    temp.DoubleClick.Add(fun e -> MessageBox.Show("Hello") |> ignore)
    temp

It creates an array of two Windows control objects, upcasting them to their base class, Control. Then it binds the first control to the control identifier. It then downcasts this to its specific type, Button, before adding a handler to its DoubleClick event, an event not available on the Control class.

Type Tests

Closely related to casting is the idea of type tests. An identifier can be bound to an object of a derived type, as you did earlier when you bound a string to an identifier of type obj:

#light
let anotherObject = ("This is a string" :> obj)

Since an identifier can be bound to an object of a derived type, it is often useful to be able to test what this type is. To do this, F# provides a type test operator, which consists of a colon followed by a question mark (:?). To compile, the operator and its operands must be surrounded by parentheses. If the identifier in the type test is of the specified type or a type derived from it, the operator will return true; otherwise, it will return false. The next example shows two type tests, one that will return true and the other false:

#light
let anotherObject = ("This is a string" :> obj)

if (anotherObject :? string) then
    print_endline "This object is a string"
else
    print_endline "This object is not a string"
if (anotherObject :? string[]) then
    print_endline "This object is a string array"
else
    print_endline "This object is not a string array"

First you create an identifier, anotherObject, of type obj but bind it to a string. Then you test whether the anotherObject is a string, which will return true. Then you test whether it is a string array, which will, of course, return false.

Type Annotations for Subtyping

As shown in Chapter 3, type annotations are a way of constraining an identifier, usually a parameter of a function, to be a certain type. What may seem counterintuitive to an OO programmer is that the form of type annotation introduced in Chapter 3 is rigid; in other words, it does not take into account the inheritance hierarchy. This means that if such a type annotation is applied to an expression, then that expression must have precisely that type statically; a derived type will not fit in its place. To illustrate this point, consider the following example:

#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

You can use this kind of type annotation to tidy up code that uses a lot of casting. For example, as shown in the "Casting" section earlier in this chapter, a lot of casting is often needed when creating a collection with a common base type, and this can leave code looking a little bulkier than it should. A good way to remove this repeated casting, as with any commonly repeated section of code, is to define a function that does it for you:

#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); }

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
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)) 2;0
      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)
    )
    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 syntax you learned in Chapter 3, except it includes member definitions that always come at the end, between the with and end keywords. 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 parameters 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 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 keyword member, in this case x.

Union types can have member functions too. You define them in the same way as for record types. The next example shows a union type, DrinkAmount, 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 available 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 provide a concise syntax to create an object that inherits from an existing type. This is useful if you want to provide 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 implementation of a class or interface while at the same time creating a new instance of it.

The syntax is similar to the alterative syntax for creating 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 followed by 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 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 interface 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 results 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 interface with one method, Compare, which takes two parameters and returns an integer that represents the result of the parameter comparison. It accepts one type parameter; in this case, you pass it a string. You can see this on the second line of the definition of the identifier comparer. After this comes the definition of the method body, which in this case compares reversed versions of the string parameters. Finally, you use the comparer by defining an array and then sorting using the comparer and displaying the "before" and "after" results in the console.

It is possible to implement multiple interfaces or a class and several other interfaces within 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 interface 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)

The previous example shows the definition of object expression that implements both the class Control and the interface IComparable. IComparable allows 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 displaying. 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 insulating 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 complexities 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

To implement an interface, use the keyword interface, followed by the interface name, then keyword with, then the code to affect the interface members, and then the keyword end. Member definitions are prefixed by the keyword member but otherwise are the same as the definition of any method or property. You can implement interfaces by either classes or structs; I cover how to create classes in some detail in the following sections, and I cover structs in the section "Structs" later in this chapter.

The next example defines, implements, and uses an interface. The class Implementation implements the interface MyInterface.

#light
type MyInterface = interface
    abstract ChangeState : myInt : int -> unit
end
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 interfaces are explicitly implemented in F#. If 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 revise the example, you 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

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 sections, 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 keyword followed by an equals sign and then the keyword class. To terminate your class definition, 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 the class, you must explicitly add a constructor. To do this, you need to add a member, which is always named new and 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 construction 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 constructor, 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 different 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 constructors, one with no parameters and one with one parameter:

#light
open System.IO

type File2 = class
    val path: string
    val innerFile: FileInfo
    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 initialize 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 internal state cannot change; it simply means you 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. However, 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 happen, F# provides the keyword mutable; when a field is defined as mutable, it can be rebound whenever the programmer chooses. The following example illustrates its usage. In this example, you see that the mutable keyword is applied to the FileInfo field so that you can change the instance of the object it refers to later. You see that if the file does not exist, it is replaced by the 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 implicitly 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 constructor. Furthermore, the body of the class may contain let bindings, which are always private to the class. Here 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. Here is a second example:

type Counter(start, increment, length) = class
    let finish = start + length
    let mutable current = start
    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 necessary 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." Inheritance allows you to extend a class that is already defined and to tweak its behavior to add new or replace 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 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
type 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 parameterless 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)
printfn
    "myObject.state = %i, myObject.otherState = %i"
    myOtherObject.state
    myOtherObject.otherState

The 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 follows, 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 are mutable. A derived class can define new methods and can override methods inherited from its base class.

Methods are defined using four keywords, either member, override, abstract, or default. The simplest way to declare a method is to use the member keyword; this defines a method that cannot 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 overridden 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 followed 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 name of the method. Next come the parameters of the method. After that comes an equals sign 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 results of this example, when compiled and executed, are as follows:


base class:
1
4
sub class:
1
1

You first implement a method, JiggleState, in class Base. The method cannot be overridden, 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 definition 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

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 implementation 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 method. After the member name comes the name of the type separated from the member name by a colon. Then follows the keyword, followed by either get or set, 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 implement 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]
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 definition. 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

You will notice from the previous example that the static methods called use the name of the type 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 following 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.

Defining Delegates

Delegates 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 functionality 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

You define structs in a similar manner to classes. The keyword class is replaced 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. Because structs 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 differences do tend to be quite small. Because they are 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 inheritance when implementing structs, so this means structs can't define virtual methods or abstract methods.

The next example defines a struct representing an IP address. Note the only difference from defining a class is that the keyword struct is used.

type IpAddress = struct
    val first : byte
    val second : byte
    val third : byte
    val fourth : byte
    new(first, second, third, fourth) =
        { first = first;
          second = second;
          third = third;
          fourth = fourth }
    override x.ToString() =
        Printf.sprintf "%O.%O.%O.%O" x.first x.second x.third x.fourth
    member x.GetBytes() = x.first, x.second, x.third, x.fourth
end

So, when should you use a classes, and when should you use a struct? A good rule of thumb is to avoid structs, using them only when really necessary, for example, when interoperating with unmanaged C/C++ code (for more details on this, see Chapter 13).

Enums

Enums allow you to define a type made up of a finite set of identifiers, with each identifier mapping to an integer. This definses a type that can then take the value associated with any one of the defined identifiers.

To define an enum, give the names of the identifiers followed by equals signs and then the values of the constants associated with the identifiers. The identifiers that are members of the enum are separated by vertical bars. To demonstrate this, an enum Scale is defined in the following example:

#light
type Scale =
|   C = 1
|   D = 2
|   E = 3
|   F = 4
|   G = 5
|   A = 6
|   B = 7

It's quite common to define enums that are intended to be combined logically. To do this, choose constants so that each number is represented by a single bit, that is, the numbers 0, 1, 2, 4, 8, and so on. F#'s binary literals are a great help here, since you can easily see how the constants can combine.

#light
[<System.Flags>]
type ChordScale =
|   C = 0b0000000000000001
|   D = 0b0000000000000010
|   E = 0b0000000000000100
|   F = 0b0000000000001000
|   G = 0b0000000000010000
|   A = 0b0000000000100000
|   B = 0b0000000001000000

The module Enum provides functionality for dealing with enums in F#; I discuss it in Chapter 7.

Summary

You've now seen how to use the three major programming paradigms in F# and how flexible F# is for coding in any mix of styles. In the next chapter, you'll look at how code is organized in F# and how to annotate and "quote" it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset