Chapter 13

Object-Oriented Programming, Part I

Main Page

Introduction

For the past 15 years or so, object-oriented (OO) design (OOD) and programming (OOP) has been the lingua franca of mainstream development. As a multidisciplinary language, F# supports a full complement of OOP constructs including structures, classes, interfaces, inheritance, etc. In this chapter, we’ll look at how to work with existing .NET types, as well as how to build our own OO types using the mechanisms F# supplies.

 Please note that the chapters on OOP assume familiarity with the concepts and terminology of encapsulation, classes, object instances, inheritance, and polymorphism.

Access Specifiers

F# supports common OO access specifiers, which give structures (and classes, etc.) a way to control how clients can interact with them. F# supports the following access control keywords:

·         public – all callers can access

·         internal – callers within the same assembly have access

·         private – callers within the same enclosing type or module can access

If you’ve been programming in OO languages for any length of time, your immediate question is “What about protected access?” F# may support the protected keyword in the future; however, if you try to use it in code today, the F# compiler will tell you it’s reserved for future use; therefore, as of this writing, this is no protected access. We use access specifiers throughout the rest of this chapter and throughout the rest of this book.

Structures

The first step down the road toward OOP takes us face to face with structures. A structure is a logically-related set of data and methods that exhibits a low-overhead representation on the stack. A structure is a .NET value type, which means it uses pass-by-value semantics, i.e., a copy is made when the structure is passed around. In the following example, we define a simple structure Book containing several simple fields:

[<Struct>]
type Book =
    val Author: string
    val Title: string
    val Price: float

 All structures are decorated with the [<Struct>] attribute, which instructs the F# compiler to apply value semantics.

Using Fields

Structures are made up of fields, constructors, and optionally, interface implementations. To declare fields, we use the keyword val. In the example above, the structure Book defines three fields: Author, Title and Price. Fields may be static and may be declared public, internal or private (see the next section of this chapter for the details on these access specifiers). The default access for fields is public. Fields may also be mutable. The general form of the val expression is:

[ static ] val [ mutable ] [ access-modifier ] field-name : type-name

To access a structure’s fields, you use dot (.) notation.

Creating Instances

Now that we have a structure, let’s talk about how to work with it. In general, we work with structures by creating instances of them via the keyword new, as demonstrated below:

let b = new Book()    


This creates a new instance of the Book structure and assigns it to the identifier b. When F# creates the new book instance b, it needs to initialize the three fields Author, Title, and Price. To initialize these field, F# needs to invoke a constructor. The first thing F# will do is to look for a custom constructor. If it finds one, it will invoke it. If is does not find one, it will use the default constructor that it has generated on our behalf behind the scenes. This defaul constructor initializes all numeric types to 0 and all non-numeric types to null. Since we have not yet defined a custom constructor for Book, F# invokes the default constructor and sets Author to null, Title to null and Price to 0.0.

Mutability

Since Book’s fields are not specified as mutable, they will remain at their default values throughout their existence. If you need the ability to modify a field over the lifetime of the application, you must declare it mutable, as in the following example:

[<Struct>]

type Book =

    val Author: string

    val Title: string

    val mutable Price: float    // now, we can adjust Price

   

Given this new definition of the Book structure, we can create instances of Book that allow the Price field to change over time. In order to actually modify mutable fields, the structure instance itself must be created using the mutable keyword, as in the following example:

let mutable b = new Book()     // note use of "mutable b" here

b.Price <- 11.50               // updated via destructive assignment

 

Mutability is granted on a field-by-field basis. Given the code above, the following line will generate a compiler error:

b1.Author <- "Jones"            // error - Author not mutable

Custom Constructors

In addition to fields, structures can define one or more custom constructors that are used to initialize their fields to default values. As mentioned previously, if you choose not to supply a custom constructor, the F# compiler will generate one on your behalf.

A custom constructor is defined using the keyword new followed by a list of arguments. The arguments may include type information; however, if they do not, F# type inference will try to determine their types based on how they’re used in the body of the constructor.

In the following example, we update the Book structure to include an explicit, custom constructor:

[<Struct>]

type Book =

    val Author: string

    val Title: string

    val Price: float

    new(a, t, p) = { Author = a; Title = t; Price = p }

A custom constructor enables a caller to pass in initialization arguments when creating a new structure instance, as shown here:

let b = new Book("Jones", "Happy Days", 11.45)

 Note that the structure’s fields do not need to be declared mutable in order to be initialized by the constructor.

Explicit constructors can contain whatever arguments you need in order to initialize your structure. The only requirement is that the body of the constructor must initialize all of the fields. If your custom constructor fails to initialize all of the structure’s fields, the compiler will generate an error.

You can create multiple custom constructors, each taking a different set of arguments. Multiple constructors provide callers with a variety of creation options. As an example, let’s update our Book structure to include two custom constructors:

[<Struct>]

type Book =

    val Author: string

    val Title: string

    val Price: float

    new(a, t, p) = { Author = a; Title = t; Price = p }

    new(a, t) = { Author = a; Title = t; Price = 12.50 }

Given the above definition, we can now create book instances as follows:

let wt = new Book("Where the Wild Things Are", "Maurice Sendak", 9.50)

let dr = new Book("The Girl with the Dragon Tattoo", "Stieg Larsson")

Of course, you can combine custom constructors with mutable fields, as in our final example:

[<Struct>]

type Book =

    val Author: string

    val mutable Title: string

    val mutable Price: float

    new(a, t, p) = { Author = a; Title = t; Price = p }

   

let mutable b1 = new Book("Smythe", "Money and You", 10.00)

b1.Title <- "Money and Your Life"

b1.Price <- 11.75

 

General Form

For the studious among us, a structure in F# takes the following form:

[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
    type-definition-elements[1]

Restrictions

A few restrictions to keep in mind – structures cannot participate in inheritance, cannot contain let or do bindings (we’ll see what these are later), and cannot recursively contain fields of their own type, although they can contain reference cells that reference their own type.

Classes

Classes represent the “heart and soul” of OOP. Classes are types that represent entities in the domain model. In OO systems, computation is largely about creating instances of classes that contain state, i.e., objects, and manipulating this state over time.

Please note that throughout this chapter, I will use the term “type” and “class” synonymously. At their core, these two terms represent the same construct and can be used interchangeable for all practical purposes.

Constructing Classes

Classes are user-defined types, introduced with the type keyword and a primary constructor. The primary constructor is defined as part of the type definition, and may or may not include parameters. The following demonstrates the humble beginnings of an F# class:

type Movie()

This tells us we have a new type called Movie and a primary constructor the takes 0 arguments ( ).

Following the primary constructor definition is an = sign and the set of expressions that form the primary constructor’s body.

The body of the primary constructor can contain zero or more let bindings, which appear at the start of the type definition, before any member definitions. Let’s take a look at a simple class definition that helps make all of this a reality:

type Movie() =

    let Title = "Paranormal Activity"

This bit of code does a few things:

1.       It introduces a new type called Movie

2.       It introduces a primary constructor that takes no arguments: Movie(). The empty parens introduce the primary constructor.

3.       It defines a single let expression that forms the body of the primary constructor.

A let expression in a constructor implicitly defines a private field or private function. This means that the field Title is private in the above example. Attributes and accessibility modifiers are not allowed, therefore we cannot change the fact that Title is private – it’s an inherent property of the let binding as used in a constructor.

To make things a little more interesting, let’s define a new Movie class that creates 2 private fields in its constructor (OK, it’s not that interesting, but bear with me).

type Movie() =

    let Title = "Paranormal Activity"

    let Director = "Orin Peli"

In addition to executing these let expressions, constructors can also execute arbitrary code using the keyword do followed by a series of expressions. In the next example, we create a class that prints out a few messages each time a new instance is created.

type Movie() =

    let Title = "Paranormal Activity"

    let Director = "Orin Peli"

    do

        printfn "Now showing %s by %s" Title Director

        printfn "We hope you enjoy the show!"

OK, so now we know how to specify a class, create a primary constructor, and execute instance-specific code the form of let and do expressions, sometimes referred to as let and do bindings.

To create a fresh instance of a class, use the (optional) keyword new. This causes a new instance to be created and initialized with the constructor, e.g.:

let m = new Movie()

 

Note that using the keyword new is optional. I tend to favor it because it’s explicit and conveys the purpose of the call; however, if it suits your style, you can omit it without error, e.g.:

let m = Movie().

Constructors with Arguments

When we create class instances, we generally want to pass in initialization information – this is, of course, the whole point behind constructors. F# supports class constructors that accept arguments, as shown in the following example:

type Movie(t, d) =

    let Title = t

    let Director = d

    do

        printfn "Now showing %s by %s" Title Director

        printfn "We hope you enjoy the show!"

Movie’s primary constructor now requires 2 arguments – a title and a director. To create instances of Movies we can execute code similar to the following:

let ho = new Movie("The Hangover", "Todd Phillips")

let pa = new Movie("Paranormal Activity", "Orin Peli")

If F# has trouble determining the types of the constructor’s arguments, you can add explicit type annotations to them. For example, we can re-write the Movie type as follows, specifying the type of t and d:

type Movie(t: string, d: string) =

    let Title = t

    let Director = d

    do

        printfn "Now showing %s by %s" Title Director

        printfn "We hope you enjoy the show!"

Constructors that accept arguments are flexible relative to how these arguments work. First, the arguments of the primary constructor are in scope throughout the entire class declaration. This means that you can use the arguments passed into the primary constructor anywhere in the class. This also means that you do not need to store the arguments - you can treat them as implicit private fields.

Second, by default, constructor arguments bind to their input by position, left to right; however, in F#, constructor arguments are implicitly named, making it easy for callers to access them directly and/or out of order, as in this next example:

type Movie(title: string, boxOfficeSales: decimal) =

    let netSales = boxOfficeSales * 0.90m   // these numbers are made up

    do printfn
        "%s grossed %f at the box office, for net sales of %f for the studio"

        title boxOfficeSales netSales

   

let m = new Movie(boxOfficeSales=100000000m, title="Jaws")

Notice here that when we call the Movie constructor, we use the arguments’ names to map the values explicitly vs. relying on their lexical ordering. We provide initial values using the arg-name=value syntax.

Constructors with Optional Arguments

Constructor arguments can be made optional by placing a question mark (?) before the parameter name. Optional arguments must come at the end of the argument list. Under the covers, F# wraps optional arguments in an Option (Some/None) type, making them naturally compatible with pattern matching.  In the following example, we make the argument director optional:

type Movie(title: string, ?director: string) =

    let getDirector : string =

        match director with

        | Some(director) -> director

        | None -> "NOT SPECIFIED"

    do printfn "title = %s, director = %s" title getDirector

Typically, when processing optional arguments, we use pattern matching to check for the presence (or absence) of a value.

When dealing with optional arguments, you often want to provide default values, in case the caller chooses to skip passing values into the function or method. To do this, you can use the function defaultArg. The defaultArg function takes the optional parameter as the first argument and the default value as the second. In the following example, the ticketPrice parameter is optional, and has a default value of 10.00.

type Movie(title: string, ?ticketPrice: float) =
    let ticketPrice = defaultArg ticketPrice 10.00
    do printfn "Welcome to %s. Your ticket costs $%f" title ticketPrice

Overloaded Constructors

To provide different creation options its clients, a type can offer more than one constructor. You can define overloaded constructors using the following syntax:

new(arg-list) = constructor-body

In the following example, the Movie class contains 2 constructors: the primary constructor that accepts 2 parameters (string and float), and an overloaded constructor that takes 1 string.

type Movie(title: string, ticketPrice: float) =
    let Title = "[" + title + "]"
    do printfn "New movie created: %s" Title
    new(t: string) = Movie(t, 10.00)

You can define as many overloaded constructors as you want. Just keep in mind that the body of the overloaded constructor must call the primary constructor.

Note that the let and do bindings defined in the primary constructor always execute, regardless of the constructor called.

Running Arbitrary Code in Overloaded Constructors

A class’s primary constructor can execute arbitrary code via a do binding; however, an overloaded constructor cannot use this mechanism. In order to execute arbitrary code in an overloaded constructor, we need to use the then keyword, as shown in the following example:

type Movie(title) =

    do printfn "Welcome to the movie: %s" title

    new(t, d) =

        Movie(t)

        then printfn "Directed by %s" d

The then keyword allows us to run constructor-specific code, after the let and do expression from the primary constructor execute. The fact that the keyword is then is a reminder of the order in which the let and do constructs execute – those from the primary execute, then those from the overload.

Self-Identifier

As part of the class definition, you can provide a self-identifier. A self-identifier is the way the class references itself in members (discussed shortly) such as properties and methods. In other words, it’s the way an F# class represents “the current instance” of itself. In C++ and C#, the self-identifier has a special name – this. In F#, there is no special name for the self-identifier – you can choose to call it whatever you’d like.

You can define a self-identifier in one of two ways. If you want the self-identifier to be available to the entire class, use the as keyword as part of the type definition. If you want the self-identifier to be available to a local member function, provide the name as part of the member declaration.

In the following example, we embellish our Movie class with a self-identifier. We will see how to use it when we discuss members later in this chapter.

type Movie() as this =

    let Title = "Paranormal Activity"

Given the above declaration, the self-identifier this enables us to refer to “this instance of the class” anywhere in the class. Note that if the self-identifier is never used (as in the above example), the compiler will issue a warning:

warning FS1183: The recursive object reference 'this' is unused. The presence of a recursive object reference adds runtime initialization checks to members in this and derived types. Consider removing this recursive object reference.

So, only use as <self-identifier> if you are going to use the self-identifier in subsequent constructs.


The Constructor Body – Details of let and do

As we’ve seen, the bodies of constructors are made up of a series of let and do expressions. Let’s (no pun intended) take a closer look at their general syntax and capabilities.

let

In a class constructor, a let expression takes the following form:

// Field
[static] let [ mutable ] binding[2]

// Function
[static] let [ rec ] binding

A let expression can define a static or non-static class member – either a field or a function. static members are those that are defined on the class itself vs. on objects of the class. As you can see from the above syntax, let expressions can create mutable and non-mutable fields, and can create recursive and non-recursive functions.

We contrast static let bindings with instance let bindings. static let bindings are guaranteed to execute at some point before the first instance of the class is created. They only execute once for the lifetime of the class.

Up to this point, we’ve used let to define private fields only. We can also use let to define private functions. These functions are accessible only to the class itself. In the following example, we use let to define a private function, calcAdmission on the Movie class.

type Movie() =

    let Title = "Paranormal Activity"

    let calcAdmission numPeople = float numPeople * 10.25

    do printfn
        "If 4 people went to see %s, it would cost $%5.2f" Title (calcAdmission 4)

do

F# class constructors can also include zero or more do bindings, which enable the execution of arbitrary code when an instance of the class is created. do bindings appear alongside let bindings, and take the following form:

[static] do expression

static do bindings are guaranteed to execute before the first instance of the class is created. They are only executed once for the lifetime of the class. The combination of static let and static do bindings form the “static constructor” of the class, and are run once, before the first instance of the class is created.

Because let bindings initialize private fields of the class, do bindings generally appear after them lexically. This enables the do bindings to access the fields that the let bindings have just initialized.

Note that if your non-static do bindings need to access members of the class to execute correctly, they can do so if the class defines a self-identifier (see next section) and all access to the class’s member are made via the self-identifier.

The net net of this binding story is that, together, the let and do bindings form the body of the primary constructor. The non-static let and do together form the instance constructor, while the static let and do together form the static constructor.

Alternate Field Creation Semantics

When you create class constructors, you can use an alternate mechanism to create fields: the val keyword. To use the val keyword to create fields, we must adhere to the following rules:

·         use the [<DefaultValue>] attribute

·         mark the field as mutable

·         access the field through a self-identifier (this in the example below).

In the following example, we demonstrate this technique using the field director:

type Movie() =

    let title = "Jaws"

    [<DefaultValue>] val mutable director: string

    member this.SetDirector =

        this.director <- "Spielberg"

The pragmatic difference between using val vs. let is that you can specifiy the access mode (public, internal, private) for a value created with val, whereas with let, your fields are intrinsicly private and their accessibility cannot be changed.[3]

Adding Members to Classes

Classes would be of limited utility if they only contained constructors and private fields. In practice, classes contain fields and functions (collectively called members) that form their public interface. You can define members on F# classes, structures, records, discriminated unions, and interfaces. We ignored adding members to these constructs earlier in the text to avoid confusion. After covering class members here, you will be in a position to add them to these other types as well. We will show examples of adding members to records, etc. as part of this chapter.

 Members are declared using the keyword member. Collectively, members represent a type’s “public face”; therefore, they have public access unless declared otherwise.

The form a member takes depends on the nature of the member, e.g., a property, method, etc. We will look at all of the possibilities in turn.

Properties

Properties are members that represent values associated with an object. They are like “smart fields” in that they have a “getter” method and a “setter” methods, a.k.a., accessors, defined. The full syntax for a property is as follows:

[ attributes ]
[ static ] member [accessibility-modifier] [self-identifier.]PropertyName
    with [accessibility-modifier] get() =
        get-function-body
    and [accessibility-modifier] set parameter =
        set-function-body

Properties may be declared static, in which case they are defined on the type itself and are limited to accessing other static members. We can also employ access specifiers to limit their visibility, although they are public by default.

In this next example, we add a ReleaseDate property to the Movie class. This property exhibits both a get accessor for reading the value and a set accessor for setting the value.

open System   // provides access to DateTime type

type Movie(title: string, released: DateTime) =

    let mutable _releaseDate = released

    member this.ReleaseDate

       with get() =

          _releaseDate

       and set(newValue) =

          _releaseDate <- newValue

As you can see here, we’ve defined a self-identifier called this via the as this following the type declaration.

There are a few things to note about this example:

·         T o implement a property, we generally create a private backing field - a field that holds the value that the property manages. Here, our backing field is called _releaseDate. By convention, I like to use an underscore in my backing field definitions. F# imposes no such restrictions.

·         Properties that implement a set member use a mutable backing; otherwise, callers won’t be able to change its value.

·         Properties are defined using a with and optional and keyword. We will discuss the specifics of these shortly.

·         The setter receives a parameter that represents the new value of the property.

 

To show how everything fits together, let’s look at the following example:

open System

type Movie(title: string, released: DateTime) =

    let mutable _releaseDate = released

    member this.ReleaseDate

        with get() = _releaseDate

        and set(newValue) = _releaseDate <- newValue

      

let mv = new Movie("Paranormal Activity", new DateTime(2010, 10, 1))

printfn "The movie will be released %A" mv.ReleaseDate

printfn "Oops..."

mv.ReleaseDate <- new DateTime(2009, 10, 1)

printfn "Correction...the movie will be released %A" mv.ReleaseDate

As you can see in the example, we use the destructive assignment operator (<-) to update the property value. Under the covers, F# translates this to a call to the property’s set function.

Properties are not required to have both getters and setters. You can implement a read-only property by defining get only, a write-only property by defining a set only. You can also include access specifiers to limit the property’s availability, e.g., using internal to limit access to those classes in the same assembly or module.

In the following example, we define a read-only property by implementing a get only. We simply do not provide a set. The syntax is quite straightforward, as shown here:

type Movie(title: string, released: DateTime) =

    let mutable _releaseDate = released

    member this.ReleaseDate // read only using a solo get()

       with get() = _releaseDate

 

We can do the same thing to create a write-only property, although this is rarely done in practice:

type Movie(title: string, released: DateTime) =

    let mutable _releaseDate = released

    member this.ReleaseDate

        with set(newValue) = _releaseDate <- newValue

Like other values, properties can be type-annotated via an explicit type declaration. In the following example, the property TicketPrice is declared as decimal via the getter member:

type Movie(title: string, price : decimal) =

    let mutable _ticketPrice = price

    member this.TicketPrice

        with get() : decimal = _ticketPrice

        and set(newValue) = _ticketPrice <- newValue

Note that the get and set functions are not considered methods per se. They are considered functions that are used to implement property members. They are accessed indirectly via the property itself and are not directly exposed outside of the property’s context.

In addition to classes, properties can be members of structures, discriminated unions, records, interfaces, and type extensions (to be discussed later) and can also be defined in object expressions (also to be discussed later). In addition, properties can be declared abstract. We will cover abstract constructs later in this chapter, when we discuss inheritance.

Combining Static and Instance Properties
There are several instances OOP when you want the class to keep track of the instances with which it is associated, e.g., keeping a count of the number of instances, limiting the creation of instances to a certain number or range, etc. The following example shows how to accomplish this in F# via static members. We’ve updated the Movie class to keep track of the number of Movies currently playing in theaters:

type Movie(title: string) =

    static let mutable numInTheaters = 0

    do numInTheaters <- numInTheaters + 1

    do printfn "Current count of movies in theaters = %d" numInTheaters

   

let m1 = new Movie("Paranormal Activity")

let m2 = new Movie("The Surrogates")

let m3 = new Movie("Toy Story 3")

Recall that static let and static do bindings are executed once per type, while the non-static bindings are executed each time an instance is created. The results of running the code are:

Current count of movies in theaters = 1
Current count of movies in theaters = 2
Current count of movies in theaters = 3

Indexed Properties

There are times when you want to store collections of items in a property, and provide indexed access to the information. F# supports indexed properties just for this purpose. They take the following form:

member self-identifier.PropertyName
    with get(index-variable) =
        get-function-body
    and set index-variables value-variables =
        set-function-body

As you can see, an indexed property takes an index variable as a parameter. It’s the property’s job to map the index to an appropriate piece of data. To demonstrate how indexed properties work, let’s add a list of actors to our Movie class:

type Movie(title, actors: string[]) =

    do printf "%s..." title

    let mutable _actors = actors

    member this.Actors

        with get(index) =

            if index < _actors.Length then _actors.[index]

            else "Unknown actor. Check the index."

        and set index value =

            if index < _actors.Length then _actors.[index] <- value

           

let m = new Movie("The Hangover", [| "Bradley Cooper"; "Ed Helms";

                                     "Zach Galifianakis"; "Justin Bartha" |])

let star = m.Actors(0)

printfn "the star of the movie is %s" star

Indexed Properties with Item

Now, although the above example works, it really doesn’t capture the spirit of indexed properties. The client is still calling accessing a distinct property and passing an index. If we want to make the object instance look like a collection of things, we can use the specially-named property Item. When we do this with indexed properties, F# provides some syntactic sugar, enabling us to access the instance as if it were a collection.

Here is a new version of the Movie class that uses the specially-named Item property. I have removed the error checking to simplify the example:

type Movie(title: string, actors: string[]) =

    let mutable _actors = actors

    member this.Item   // <<< specially-named property

        with get(index) = _actors.[index]

        and set index value = _actors.[index] <- value

           

let m = new Movie("The Hangover", [| "Bradley Cooper"; "Ed Helms";

                                     "Zach Galifianakis"; "Justin Bartha" |])

                                    

let star = m.[0]    // <<< now we can use the instance like a collection

printfn "The star of the movie is %s" star

When we access m.[0], behind the scenes, F# translates this into a call to m.Item.get(0). This syntax sugaring enables clients to treat instances like indexed collections.

Indexed Properties with Multiple Indices

Indexed properties can have more than one index variable. This is useful when dealing with 2D arrays and other matrix-like structures. For example, if we build a 2D array that contains a multiplication table (think back to 4th or 5th grade), we can access the product of two numbers simply by accessing the intersection of the row and column specified by the numbers we’re multiplying. We can combine this with the specially-named property Item as well, as in the following example:

type MTable() =

    let _table = Array2D.zeroCreate<int> 12 12

    do for x = 1 to 12 do for y = 1 to 12 do _table.[x - 1, y - 1] <- x * y

    member this.Item

        with get(a, b) = _table.[a - 1, b - 1]

       

let m = new MTable()

let product = m.[3, 7]

From the F# Interactive Console: val product : int = 21

Methods

A method is a function, a.k.a., a behavior,  that is associated with a class. In OOP, methods are the primary mechanism used to expose functionality and behaviors of objects and types. Methods may be instance methods, virtual instance methods, static methods, or abstract methods. In this section, we will cover static and instance methods. We will cover virtual and abstract methods once we’ve covered the F# inheritance model.

Static methods

Static methods are those that are defined using the keyword static, which binds the method to the type itself vs. instances of the type. In other words, all instances of the type share the type’s static method. Static methods can access static members, and are callable only through the type itself. To define a static method, we use the following syntax:

[ attributes ]
static member [inline] method-name parameter-list [ : return-type ] =
    method-body

static methods are useful when proper execution is independent of the state of a given instance, i.e., when instance state does not affect the outcome. Many mathematical members, e.g., square root functions, etc., are implemented like this.  In the follow example, let’s write a DistanceConverter type that converts between miles and kilometers to illustrate the idea:

// 1 kilometer = 0.621371192 miles, 1 miles = 1.609344 kilometers

type DistanceConverter() =

    static member KilometersToMiles k: float = k * 0.621

    static member MilesToKilometers m: float = m * 1.609

let x = DistanceConverter.KilometersToMiles 10.0
let y = DistanceConverter.MilesToKilometers 8.0

Notice that static methods do not include a self-identifer – we use member KilometersToMiles instead of member this.KilometersToMiles. static members, by their nature, do not accept self-identifiers, since they are not associated with any instance. In contrast to static methods, we have instance methods, discussed next.

Instance Methods

Instance methods are those that are associated with each instance of the class. Instance methods take into account object state, and may change it during the course of execution. They are the “standard” methods for objects. In F#, we define instance methods using the following syntax:

[ attributes ]
member [inline] self-identifier.method-name parameter-list [ : return-type ]=
    method-body

The two main differences in this syntax vs. static methods are: the absence of the static keyword and the introduction of a self-identifier. Let’s create an updated Movie class that implements a single instance method, CalcTicketPrice.

type Movie(title: string) =

    member this.CalcTicketPrice =

        if title.ToLower() = "jaws" then 7.50 // old movie

        else 10.25  // more like today's prices

 

let m1, m2 = new Movie("The Hangover"), new Movie("Jaws")

let p1, p2 = m1.CalcTicketPrice, m2.CalcTicketPrice

 

Overloaded Methods

F# types also support overloaded methods. A type defines overloaded methods when it declares two or more methods with the same name but taking a different number of arguments or the same number of arguments of differing types.

In F# we tend to use optional arguments instead of overloaded methods; however, overload methods can provide a convenient way to create methods that take completely different arguments. Let’s look at a few examples:

type Movie(title: string, director: string, zip: string) =

    member this.Find(t, d) =

        if title = t && director = d then printfn "Movie found by title and director"

        else printfn "Cannot find movie by title and director"

    member this.Find(z) =

        if zip = z then printfn "Movie found by zip code"

        else printfn "Cannot find movie by zip code"

       

let m = new Movie("Paranormal Activity", "Oren Peli", "01776")

m.Find("Paranormal Activity", "Joe Smith")

m.Find("Paranormal Activity", "Oren Peli")

m.Find("01776")

Overloaded methods can have the same number of parameters, so long as at least one is of a different type; however, due to the fact that F# can curry functions, we generally want to specify our argument list in tupled form:

// Define a class with overloaded methods
type
Foo() =

    member this.bar(x: int) = ()

    member this.bar(x: string) = ()

    member this.bar x = ()

    member this.bar(x: int, y: string) = ()

    member this.bar(x: int, y: float) = ()

 

// Call overloaded methods

let f = new Foo()

f.bar(1)

f.bar("s")

f.bar 100M

f.bar(100, "s")

f.bar(200, 2.34)

f.bar true

 

You can define as many overloaded methods on your classes as you need to.              

Events

An event is a messages that one object sends to one or more other objects. An event signals that “something happened.” When an object sends an events, we say that the object “fired the event” or “raised the event.” The object that fires the event is called the event sender or the event source. The object receiving the event is called the event target, event sink or listener. Listeners implement event handler functions that are called in response to an event being fired.

Events are characterized as distinct from properties and methods in that clients do not call event members.  Instead, clients register interest in events that might eventually be raised by an event sender. With event communication, senders are disconnected from listeners. The sender does not know which object or method will receive the events it raises. To accommodate this “publish and subscribe” model of programming, there needs to be a broker between the sender and its receiver(s).  Under the covers, .NET uses the delegate mechanism for this purpose.

Creating Delegates to Be Used with Events

For the purposes of discussing events, we can briefly describe delegates here.[4]  In F#, we use the following syntax to define delegates:

type delegate-type = delegate of type -> type

The delegate type you create is designed to wrap, i.e., call, a function of the same type. For example, if we have a function that accepts an integer value and returns a float, e.g.:

let f n =

    float n / 2.0

We can create a delegate type that can call this function:

type FDelegateType = delegate of int -> float

To call a function through a delegate, we need to create a new instance of the delegate and “wrap” the target function, e.g.:

let fd = new FDelegateType(f)

To call the function through the delegate, we use the delegate’s Invoke method:

let result = fd.Invoke(10)

Of course, we can use delegates to wrap lambda functions as well. We could have written the above example as:

type FDelegateType = delegate of int -> float

let fd = new FDelegateType(fun n -> float n / 2.0)

let result = fd.Invoke(10)

Creating Events

To create an event, you use the new Event<'delegate, 'argument> construct. This creates and returns an event object that senders can subsequently fire. The first parameter to the Event constructor is a delegate type that defines the listener function. The second parameter is the type of argument the event sends along when it’s fired, i.e., information attached to the event sent to the listener function.

When creating delegates to be used with events, we’re expected to follow certain conventions. First, our event delegates are expected to take two parameters – the source, i.e., the object raising the event, which must be specified as type obj, and the event argument. The event argument can be of any type; however, it’s mostly either a simple type, e.g., string, or an object derived from System.EventArgs. The event delegate is expected to return unit. In the following example, we create a StockAlerter class that implements an event indicating when a given stock should be sold.

The first thing we’ll do is create the delegate type, based on the rules defined above:

// Event delegate. Follows expected delegate conventions: obj * args -> unit.

type StockAlertDelegate = delegate of obj * string -> unit

This delegate type tells us that the listener function accepts two parameters: the sender, which must be specified as type obj, and the event argument. Here, we are choosing to pass a simple string. We can use this delegate and argument to implement create an appropriate Event instance in the StockAlerter class:

// Event source. Raises StockAlert events.

type StockAlerter(symbol: string) =

    do printfn "Watching %s" symbol

    let alertEvent = new Event<StockAlertDelegate, StockAlertEventArgs>()

After defining an event, we need a way for clients, a.k.a. subscribers, to register interest in the event. To make our events visible to clients and eligible for subscription, we need to publish the event. We do this via a public member of the class and the Publish method of the event class:

member this.StockAlertEvent = alertEvent.Publish

Clients access the alert event via the public member StockAlertEvent. The only thing missing is a way to actually fire the event. Generally speaking, the event source implements some internal algorithm that results in it firing an event, e.g., it might check the value of a stock every five minutes and fire an event if the price changes by some specified percentage. For our purposes, we’ll create a function that fires the event whenever it’s called. The entire StockAlerter class is shown below:

// Event source. Raises StockAlert events.

type StockAlerter(symbol: string) =

    do printfn "Watching %s" symbol

    let alertEvent = new Event<StockAlertDelegate, string>()

    member this.StockAlertEvent = alertEvent.Publish

    // Fire event. this=sender=StockAlerter instance, formatted string is argument

    // passed to client handler function.

    member this.SellStock() = alertEvent.Trigger(this, sprintf "sell %s!" symbol)

When we define events that need to carry extra information to their handlers, we implement a class that derives from System.EventArgs, e.g.:

// Event argument. Carries information about the event.

type StockAlertEventArgs() =

    inherit System.EventArgs()

    member this.message = "Time to sell!"

   

We then use the class type as the second parameter to the delegate definition:

 

// Event delegate. Follows expected delegate convention: obj * args -> unit.

type StockAlertDelegate = delegate of obj * StockAlertEventArgs -> unit

 

Whenever our code fires an event, it needs to pass along the sender and an instance of the appropriate event argument.

Listening for Events

Events are useless unless something is listening for them to occur. To continue our stock alerting example, let’s create a StockBroker class that implements an event listener:

// Event sink.

type StockBroker() =

    // Create a StockAlerter

    let sa = new StockAlerter("MSFT")

   

    // Create an event listener via creating the appropriate delegate

    let eventListener = new StockAlertDelegate(
        fun sender msg -> printfn "%A tells us to: %s" sender msg)

    do

        // Add listener to event's list of listeners

        sa.StockAlertEvent.AddHandler(eventListener)

       

        // Cause event to fire. Normally, the StockAlerter would fire its

        // own event independtly. We're doing this here to demonstrate

        // the mechanism.

        sa.SellStock()

To add a listener function, we access the source event, e.g., sa.StockAlertEvent, and call it’s AddHandler method. AddHandler adds the given delegate to the event’s internal list of listeners. When the event fires, the underlying event class iterates over its list of listeners and calls each one in turn via the stored delegate.

When a client no longer cares to receive events from a source, it need to unregister its interest in the given event. To unregister interest in an event, the client can call the event’s RemoveHandler method, as shown:

type StockBroker() =

     // …   

    // Unregister

    sa.StockAlertEvent.RemoveHandler(eventListener)

       

    // Nothing printed now since we’re not listening anymore

    sa.SellStock()

Since the Event class maintains an internal list of delegates to the client handler functions, we want to unregister handlers whenever the client handlers go out of scope, or whenever we no longer need an  active event connection. If we are not careful about disconnecting our event listeners from our event sources, we can end up crashing the application, since the event source may try to call a listener function that no longer exists.

Observable Events

As of the latest release, F# events are first-class citizens that implement the IObservable interface. From Don Syme’s blog on the CTP 1.9.7.8 release:

The IObservable and IObserver interfaces provide a general interface over observable data, which can be easily composed.  These interfaces are provided in .NET4.0, and are available in F# for .NET2.0. 

1st class events in F# now implement the IObservable interface, enabling them to interoperate with other IObservable programming models.  A new Observable module in the F# library provides compositional functions for manipulating general Observable data:

Observable.merge
Observable.map
Observable.filter
Observable.partition
Observable.split
Observable.choose
Observable.scan
Observable.add
Observable.subscribe

What this means to us is that we can use the Observable module to work with events in a functional manner. For example, let’s suppose we want to limit the stock events we care about to those whose associated stock symbols are 3 characters exactly. We can use the Observable.filter method to do just this:

// Event delegate.

type StockAlertDelegate = delegate of obj * string -> unit

 

// Event source. Raises StockAlert events.

type StockAlerter(symbol: string) =

    do printfn "Watching %s" symbol

    let alertEvent = new Event<StockAlertDelegate, string>()

    member this.SellStock() = alertEvent.Trigger(this, symbol)

   

    [<CLIEvent>]   // <<< .NET and Observable friendly

    member this.StockAlertEvent = alertEvent.Publish

   

// Event sink.

type StockBroker(sa: StockAlerter) =  

    // Create an event listener via creating the appropriate delegate

    let eventListener = new StockAlertDelegate(
        fun sender msg -> printfn "%A tells us to: %s" sender msg)

    let filteredEvent =

        sa.StockAlertEvent

            |> Observable.filter(fun (sym: string) -> sym.Length = 3)

                // only watch symbols with 3 letters

            |> Observable.add(fun msg -> printfn "Filtered event: SELL %s" msg)

               // hook up event handler

    do        

        sa.SellStock()

               

// We expect to see an IBM message. We do not expect to see a MSFT message.

let saIBM, saMSFT = new StockAlerter("IBM"), new StockAlerter("MSFT")

let sbIBM, sbMSFT = new StockBroker(saIBM), new StockBroker(saMSFT)

 

In order to work with events via the Observable module, the events must be marked with the [<CLIEvent>] attribute, making them .NET-friendly. Additionally, to hook up a listener to an Observable, use the Observable.add method, as demonstrated above. Please consult the MSDN documentation for the fully skinny on Observable.

ignore

To be honest, I wasn’t sure where to discuss the ignore keyword, so I thought it would be somewhat appropriate here, in the section in events and event processing.  To tell F# that you want to ignore the result of an expression and always return unit, you can use ignore.

This can be helpful to ignore events (thus its introduction here), return unit where the normal flow of code returns a non-unit, etc. Here are some examples of using ignore:

let test = ignore 10                // test = unit

System.Console.ReadKey() |> ignore  // return type is now in line with expectations

Use ignore when you want to return unit wherever it’s expected. For example, if you fail to use |> ignore in the example above, F# will warn you that The expression should have type 'unit', but has type 'System.ConsoleKeyInfo'.”


Specifying Generic Class Types

When defining a class, you can specify that it accepts one or more type parameters, much like the built-in collection classes do. For example, if we wan to implement a class that compares two objects of the same type, we could specify the class as follows:

type MyComparer<'a>() =

    member this.Compare(x: 'a, y: 'a, f) =

        f x y

As you can see, we specify generic type parameters in angle brackets(<>) as part of the type declaration. In this example, using generics helps to express and enforce the intent of comparing only objects of the same type. In this implementation, we supply the elements to compare as well as a comparison function that is subsequently applied.

Note that there is an alterate syntax for specifying generic parameters, where the generic parameters are specified before the type name. To rewrite the MyComparer type using this alternate syntax, we would write it as follows:

type 'a MyComparer() =

    member this.Compare(x: 'a, y: 'a, f) =

        f x y

In general, I prefer the angle-bracket syntax; however, you will see both in this book and in other F# code.

Wildcard as Type Argument

When building a generic type or function, you can instruct the compiler to infer the type argument by using the wildcard symbol (_), instead of a named type argument. This is shown in the following code.

let printSequence (s: seq<_>) =

    Seq.iter (fun elem -> printf "%s " (elem.ToString())) s

This form is generally reserved for cases when the code does not reference the type argument, as in this example. We could just as easily have written the example this way:

let printSequence2 (s: seq<'a>) =

    Seq.iter (fun elem -> printf "%s " (elem.ToString())) s

And it would have worked identically.

Generic Type Constraints

When dealing with generic types, there are times when you may want to restrict or constrain the types that can be used. F# supports the following flavors of constraints: type constraints, null constraints, explicit member constraints, constructor constraints, value type constraints, reference type constraints, enumeration type constraints and delegate constraints. These are summarized in the MSDN documentation found here.

We need to use genetic type constraints when our code requires a feature that is available on the constraied type but is not guaranteed to be available on a generic type. For example, if our code depends on the generic type to implement a particular interface, e.g.., IComparable, we can constain the generic type argument to accept only those classes that implement the IComparable interface. Let’s take a look at a few of the most often used constraints and provide an example of their use.

Type Constraints
Type constraints are the most often used of the constraint family. A type constraint limits the provided generic arguments to be equal to or derived from the type specified. It is also used to constrain the provided type argument to a type that implement a given interface. In the following example, the generic type argument is limited to those types that implement the ILoveProgramming interface (we discuss interfaces later in this chapter):

type ILoveProgramming =

    abstract Program: unit -> unit

   

type Programmer<'a when 'a :> ILoveProgramming>() =

    do printfn "I am a programmer who loves to program!"

Note the use of the symbol ( :> ). Among other things, it is used in the type constraint syntax to specify the type to which the generic must conform.

For fine-granied control over generic type arguments, we can combine constraints. In the following example, we can only construct instances of the MyCollection via by supplying arguments that are Stacks (found in System.Collections) implementing the IDisposable interface:

open System

open System.Collections

 

type MyCollection<'a when 'a :> IDisposable and 'a :> Stack>() =

    do printfn "We only accept disposable stacks here, my friend."

Of course, if your type accepts multple type arguments, you can constrain each of them individually, as in the folllowing example:

open System.Collections

open System.Collections.Generic

 

type MyCollection<'a, 'b when 'a :> IEnumerable<'a> and 'b :> ArrayList>() =

    do printfn "'a must implement IEnumerable, 'b must be an ArrayList of some sort."

Constructor Constraints

The constructor constraint is used to ensure that the provided type has a public, default constructor. A default constructor is one that takes no arguments. I guess a more accurate name for this constraint is the “public default constructor constraint.”

It’s possible that a .NET class not implement a a public, default constructor. This happens when the class author has implemented a public constructor that takes parameters. In these cases, the compiler does not generate a default constructor.

The constructor constraint provdes useful when creating collections of objects, where the absence of a default constructor can cause problems. In the following example, the generic argument that we use to create MyCollection must implement a public, default constructor:

type MyCollection<'a when 'a: (new : unit -> 'a)>() =   

    do printfn "default constructor constraint applied"

The other type constraints adhere a similar syntax and usage pattern.

Mutually Recursive Class Definitions

If you need to define types that reference each other and create a circular dependency, you need to string together the type definitions by using the and keyword. The and keyword replaces the type keyword on all except the first definition, as shown here:

type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...

In the following example, we define two classes, Student and University, each needing to know about the other.

type Student(ssn: string, u: University) =

    let _ssn, _u = ssn, u

and University() =

    member this.EnrollStudent(s: Student) = ()

General Form

As a summary, here is the general from of a class:

type [access-modifier] type-name [type-params]( parameter-list ) [ as identifier ] =
    [ class ]
        [ inherit base-type-name(base-constructor-args) ]
        [ let-bindings ]
        [ do-bindings ]
        member-list
        ...
    [ end ]

Here is a quick run-down on the class declaration’s piece-parts:

·         type – keyword that introduces a new type. We’ve seen this before with records, etc.

·         access-modifierpublic, internal or private.

·         type-name – the class name.

·         type-params – used when you want to add generic type parameters to be used in the class, e.g., <'a>.

·         parameter-list – used to create instances of the class (what you pass to the constructor).

·         class – optional keyword introducing the class body. Pairs up with the optional end keyword.

·         inherit – used to specific this class’s base class, if any.

·         let-bindings  - enables us to declare fields and function values that are local to the class.

·         do-bindings – includes code to be executed when the an object of the class is constructed.

·         member-list – contains constructors, static method declarations , instance method declarations, interface declarations, abstract bindings, and property and event declarations.

·         as identifier – provides a local alias that the class itself can use in its definition – a “self identifier” akin to the this keyword in C++ and C#.

·         end – optional keyword that marks the end of the class body. Pairs up with the optional class keyword.

Adding Constructors and Member Functions to Aggregate Types

Now that we’ve discussed classes and their component parts, let’s briefly revisit the aggregate types and demonstrate how to add members, e.g., properties and methods, to them.

Structures

As discussed earlier in this chapter, structures, like classes, can have a primary constructor, and you can provide additional constructors by using keyword new. Note that structures always have a default constructor (that is, one with no arguments) generated by F#. The default constructor initializes all the fields to the default value for that type, zero for numeric types and null for object types. Any constructors that you define for structures must have at least one argument so that they do not conflict with the default constructor that F# generates for you. Structures can also include members such a properties, methods and events, as shown below (sans events, for brevity).

[<Struct>]

type Movie =

    val Title: string

    val Director: string

    val ReleaseYear: int

    new(t, d, y ) = { Title = t; Director = d; ReleaseYear = y}

   

    member this.Marquee

        with get() =

           "*** " + this.Title + " by " + this.Director + " released " + string
              this.ReleaseYear + " ***"

       

    member this.CalcTicketPrice() =

        if this.ReleaseYear >= 2009 then 11.00 else 9.00

       

let mv = new Movie("Where the Wild Things Are", "Jonze", 2009)

printfn "%s" mv.Marquee

let p = mv.CalcTicketPrice()

printfn "That'll be $%2.2f, please!" p

Discriminated Unions

Although discriminated unions do not support constructors per se, individual discriminators can accept initialization parameters. In addition discriminated unions can include other members, e.g., properties, methods, and events. In the following example, we define a discriminated union Shape that includes a method Draw:

type Shape =

    | Circle of double

    | Triangle of double * double * double

    | Square of double

   

    member this.Draw() =

        match this with

        | Circle(x) -> printfn "Drawing circle of radius %f" x

        | Triangle(x,y,z) -> printfn "Drawing triangle with sides %f %f %f" x y z

        | Square(x) -> printfn "Drawing a square with diagonal %f" x

       

let c, t = Circle(10.0), Triangle(3.0, 4.0, 5.0)

c.Draw(), t.Draw()

Records

Similar to discriminated unions, records                 do not support constructors explicitly, but employ the creation semantics as discussed previously. Records also support properties and methods. Records are different from classes in that their fields are automatically exposed as properties.  Let’s define a Student record that demonstrates these capabilities:

type Student =

    {

        Name: string;

        YearEntered: int;

        mutable GPA: float;

    }

    member this.GraduationYear

        with get() = this.YearEntered + 4

       

    member this.UpdateGPA(newGPA: float) =

        this.GPA <- newGPA

       

let s = { Name="Jim Kress"; YearEntered=2008; GPA=3.1 }

s.UpdateGPA 3.8

printfn "%s will graduate in %d with a %f GPA" s.Name s.GraduationYear s.GPA

When to Use Classes, Unions, Records, and Structures

Given the variety of types to choose from, it can be hard to know which ones to address a given problem or scenario. The following rules of thumb are adapted from the MSDN documentation:

·         Classes are the lingua franca of OOP, and are used heavily throughout the .NET framework. If your application is object-oriented, or needs to interact closely with other .NET components written in C#, VB. NET, etc., classes are a good choice.

·         If you are not interoperating closely with OO code, you should consider using records and discriminated unions. A discriminated union, together with appropriate pattern matching code, can often be used as a simpler alternative to an object hierarchy.

·         Records have the advantage of being simpler than classes, but records are not appropriate when the demands of a type exceed what can be accomplished with their simplicity. Records are basically simple aggregates of values that can perform custom actions. They do not contain private fields, and cannot participate in inheritance or interface implementations. Although members such as properties and methods can be added to records to make their behavior more complex, the fields stored in a record are still a simple aggregate of values.

·         Structures are also useful for small aggregates of data, but they differ from classes and records in that they are .NET value types. Classes and records are .NET reference types. Therefore, structures are appropriate for frequently accessed data when the overhead of accessing the heap is a concern.

What You Need to Know

·         F# supports a full complement of OO technologies include structures, classes, interfaces, inheritance, etc.

·         Structures in F# are stack-based value types that enable you to logically and semantically group related data. Structures can have implicit or explicit constructors, immutable fields, and mutable fields.

·         You use the [<Struct>] attribute to declare a structure.

·         To create fields, you use the val keyword.

·         You create instances of structures using the new keyword.

·         Fields marked as mutable are modified via a mutable structure instance.

·         F# supports the access specifiers public, internal and private. protected is listed as a reserved keyword for the future.

·         F# supports classes. You use the type keyword to define them.

·         Classes support the standard OO structures of constructors, overloaded constructors, properties, methods, events, interfaces, statics, etc.

·         F# does not have a specific keyword for identifying object instances – you can define your own. Many people tend to use this based on past experience.

·         You create instances of classes via the new keyword (new is optional). Many people tend to use new because it’s explicit and clear.

·         You can use generic parameters when defining classes. You can specify them in one of two ways: type 'a MyClass() or type MyClass<'a>.

·         Generic type can be constrained, e.g., to being inherited from a given ancestry using the :> operator.

·         F# supports flexible types, which constrain a type to a given hierarchy. The syntax is #type. You see flexible types used often in conjunction with the Seq type to accommodate different IEnumerables.

·         In addition to classes, you can add constructors and members to aggregate types such as structures, unions and records.

 

 

 



[1] There is also a syntax used when #light is not being used. As mentioned earlier, we are eschewing the heavy syntax in this text. Please see the MSDN documentation for details on the other syntax option.

[2] In some older documentation, you will see that the let keyword can include an “and” conjunction. This is for compatibility with OCaml, and is deprecated as of this writing.

[3] Although, you can create a public property that accesses the field.

[4] We cover them more fully in the next chapter.

 

Feedback

We welcome your feedback. If you have comments or questions about this chapter, please feel free to e-mail us at

Keep Reading

Next Chapter...