Chapter 14

Object-Oriented Programming, Part II

Main Page

Introduction

Now that we understand class fundamentals, we’re in a position to explore F#’s support for more advanced OOP concepts such as inheritance and type extensions. We begin by discussing how F# implements inheritance, which leads us steadily into a lively discussion about abstract classes and interfaces. We then discuss casting, type conversions, and type extensions. We conclude this chapter with a brief discussion of attribute classes.

Inheritance

Some of the power and elegance (and problems!) of OOP comes in the form of inheritance – the ability to base a new type on the structure and function of a previously defined type. While the philosophies governing the proper use of inheritance are beyond the scope of this book, how to implement inheritance in F# is squarely within our purview.

To create a new type that is based on an existing type, we use the inherit keyword. In the following example, we create a class called MyEncryptedFile that is based on an MyFile:.

type MyFile(name: string) =

    do ()

         

type MyEncryptedFile(ename: string) =

    inherit MyFile(ename) // base constructor requires args, so pass

    do ()  

A class can have at most one direct base class, although it can have a long line of ancestors. If a class does not inherit from another class explicitly, it implicitly inherits from .NET’s ultimate base class, Object. In F# the Object class is represented by the obj keyword. You can represent any object via obj, as in the following example:

let o = new obj();

printfn "%s" (o.ToString())

Note that F# also supports the keyword null to indicate the absense of an object.

When a derived class inherits from a base class, all of the non-private members of the base class are available to users of the derived class automatically. If the derived class needs to access a member of the base directly, it uses the base keyword.  Let’s flesh out our HttpManager example to demonstrate these mechanisms in action:

 

type MyFile(name: string) =

    member this.Create() = printfn "MyFile created"

    member this.Close() = ()

 

type MyEncryptedFile(ename: string) =

    inherit MyFile(ename) // base constructor requires args, so pass

 

    member this.Create() =

        base.Create()                         // create file using base implementation

        printfn "MyEncryptedFile created"   // custom work here

 

Notice that when the base class constructor expects an argument (as our MyFile class does), the derived class needs to pass the argument this along as part of the inheritance declaration construct.

Overrides

There are many times in OOP where a derived class re-implements a method defined in one of its base classes (more than one coming from its inheritance chain, not from multiple parents). If a derived class implements a member function with the same name, number and type of parameters as that of it base class, the derived class masks or hides the base’s implementation. Let’s look at an example:

type MyFile(name: string) =

    member this.Create() = printfn "MyFile created"

    member this.Close() = ()

 

type MyEncryptedFile(ename: string) =

    inherit MyFile(ename)

   

    // hides base implementation

    member this.Create() =

        printfn "MyEncryptedFile created"

In this example, the MyEncryptedFile’s Create method overrides MyFile’s implementation. There’s nothing too crazy about this – things works you’d expect. If you create a MyFile instance and call its Create method, “MyFile created” will be printed. If you create a MyEncryptedFile instance and call its Create method, “MyEncryptedFile created” will be printed. So far, so good.

The wrinkle happens when we have a base class object that actually refers to a derived class, i.e., a MyFile that refers to a MyEncryptedFile. Given the definition of the MyFile and MyEncryptedFile classes above, let’s demonstrate what we mean:

let mutable mf = new MyFile("foo")

mf.Create()

let mef = new MyEncryptedFile("bar")

mef.Create()

mf <- mef   // base class now holds derived class

mf.Create() // what's printed?

In order for F# to call the correct method based on the actual type of the object being referenced, we must make the Create method virtual. Let’s see how to do that now.

Abstract & Virtual Methods

A virtual method is one that participates in dynamic dispatch. Dynamic dispatch enables us to declare an instance of a base class, assign into it an instance of a derived class, and have the correct method called when we call an overridden method through the base reference. Instead of hiding the base method, the derived class can override it.

To tell F# that a method is override-able and should participate in dynamic dispatch, we make the base member virtual by using the abstract keyword. We use the keyword abstract regardless of whether or not the base class supplies an implementation (this is different from other OO languages like C#). To demonstrate the idea, let’s change the definitions of our file classes a bit:

type MyFile(name: string) =

    abstract member Create: unit -> unit

    default this.Create() = printfn "MyFile created"

    member this.Close() = ()

 

type MyEncryptedFile(ename: string) =

    inherit MyFile(ename)

   

    // overrides base implementation

    override this.Create() =

        printfn "MyEncryptedFile created"

Things to notice about this example:

·         The syntax for declaring an abstract member is:
abstract member member-name : type

·         Here, Create is a method that accepts no arguments (unit) and returns nothing (unit).

·         To provide a default implementation for an abstract (a.k.a., virtual) member in the base class, we use the following syntax:
default self-identifier.member-name arguments = member-body

·         In the derived class, we have the option of supplying our own implementation. To do this, we use the following syntax:
override self-identifier.member-name arguments = member-body

If we omit the default implementation in the base class, the entire base class becomes abstract, which means you cannot instantiate an instance of the type. This means that if a developer wants to implement the functionality defined in the base class, he must do so via creating a derived class and implementing all the abstract methods defined in the base. Additionally, if the derived class chooses not to override the existing base implementation, it uses the implementation defined in the base class.

If we now re-run the sample, we get a different (and more accurate) result:

let mutable mf = new MyFile("foo")

mf.Create()

let mef = new MyEncryptedFile("bar")

mef.Create()

mf <- mef   // base class now holds derived class

mf.Create() // what's printed?

Abstract Classes

Abstract classes are ones that leave some or all members unimplemented, so that derived classes can provide implementations. In F#, we define abstract classes using the following syntax:

[<AbstractClass>]
type [ accessibility-modifier ] abstract-class-name =
    [ inherit base-class-or-interface-name ]
    [ abstract-member-declarations-and-member-definitions ]

    // Abstract member syntax.
    abstract member member-name : type-signature

Abstract classes represent common protocol and functionality for a family of related classes. They are useful for defining what a family of classes needs to do, and may supply some default implementations for one or more of their members. Derived classes can override default implementations. We saw a glimpse of this in the previous example.

A class is considered abstract only if there are abstract methods that are declared but not defined (have no default implementations). Only use the AbstractClass attribute when your class has undefined abstract methods.

In the following example, we define an abstract class Clock that serves as the template for a family of different clock types. We also define two derived classes that represent a 12-hour and a 24-hour clock respectively:

[<AbstractClass>]                              

type Clock(dt: System.DateTime) =

    member internal this._dt = dt

    abstract GetTime : unit -> string

    default this.GetTime() = dt.ToString()

    abstract RingAlarm: int -> unit

   

type Clock12() =

    inherit Clock(System.DateTime.Now)

    override this.RingAlarm(vol) =

        printfn "(Volume at %d) Ring, ring, ring..." vol

           

type Clock24() =

    inherit Clock(System.DateTime.Now)

    override this.RingAlarm(vol) =

        printfn "(Volume at %d) Buzzzzzzzzzzzzzz" vol

    override this.GetTime() =

        base._dt.ToString("HH:MM")

In this example, we see the AbstractClass attribute applied properly, using internal members to support enapsulation, using a default member to support a default implementation, overriding a default member, and leveraging the base keyword.

Note that abstract classes themselves may inherit from other abstract classes. The entire chain of classes is considered to be abstract and is terminated only by a concrete class, i.e., one that implements the specificed class protocols in their entirety.

Interfaces

Like abstract classes, interfaces define a set of behaviors.  The fundamental difference between abstract classes and interfaces is that interface members do not (cannot) define any implementation[1] – they are pure protocol that classes or object expressions (covered later in this chapter) can choose implement.

Defining Interfaces

We define an interface using the following syntax:

// Interface declaration
[ attributes ]
type interface-name =
    [ interface ]
    abstract member1 : [ argument-types1 -> ] return-type1
    abstract member2 : [ argument-types2 -> ] return-type
    ...
    [ end ]

Let’s use this syntax to define an interface that is representative of a message in a messaging system, e.g., SMS or e-mail.

type IMessage =

    abstract SetRecipient : string -> bool

    abstract SetBody : string -> unit

    abstract Attach : obj -> unit

    abstract Send : unit-> bool

 

Note that with the #light syntax, the interface…end construct (as shown in the general form) is optional. If you do not supply an interface…end, the F# compiler will attempt to determine if the element being defined is a class or an interface based on context. If you run into ambiguous situations, you can always specify that you’re creating an interface by using interface…end semantics.

Having defined this interface, we can write a class to implement it. Of course, more than one class can choose to implement a given interface.

Implementing Interfaces Using Classes
A class implements an interface using the following syntax:

// Implementing inside a class type definition
interface interface-name with
    member self-identifier.member1 argument-list = method-body1
    member self-identifier.member2 argument-list = method-body2

In the following example, we create a class that implements the IMessage interface defined above:

type EMailMessage() =

    interface IMessage with

        member this.SetRecipient recipient = true

        member this.SetBody body = ()

        member this.Attach entity = ()

        member this.Send() = true

Each class that implements this interface can provide its own implementation, completely independent of all other implementations.

Classes can implement more than one interface, i.e., they are not restricted to a implementing a single interface. This enables classes to be behavior-compatible (and substitutable) without needing to be part of the same inheritance hierarchy.[2] Here is a short example:

type ILoveFSharp =

    interface   // just to show the syntax

        abstract Test: unit -> unit

    end

   

type IRobot =

    abstract Activate: string -> bool

   

type Robot() =

    interface IRobot with

        member this.Activate(name: string) = true

    interface ILoveFSharp with

        member this.Test() = printfn "test OK"

In this example, the Robot class implements the IRobot and ILoveFSharp interfaces.

Interfaces with Generic Parameters

You can define interfaces that accept generic type parameters. In the following example, we define a generic interface for working with basic collections.

type ICollection<'a> =

    abstract AddElement: 'a -> unit

    abstract RemoveElement: 'a -> bool

    abstract FindElement: 'a -> 'a

This interface accepts a type argument and uses this argument throughout its definition, as appropriate.

Interface Hierarchies

Interfaces can inherit from other interfaces, forming an interface hierarchy.  This works for both non-generic and generic interfaces. This enables us to define an interface, use it as the basis of a specialized interface, ad infinitum. In the following example, we define a base interface called ICollection (which happens to be generic – this is immaterial to the example). We then create a new interface, IList, that’s based on (derived from) ICollection. Lastly, we create a class that implements IList, which means it needs to implement all the members of IList, which includes the members from ICollection plus the members defined within IList itself.

// Interface with generic type parameter

type ICollection<'a> =

    abstract AddElement: 'a -> unit

    abstract RemoveElement: 'a -> bool

    abstract FindElement: 'a -> 'a

   

// Interface inheritance (with generic types)
type
IList<'a> =

    inherit ICollection<'a>

    abstract Iterate: (unit -> 'a) -> unit

        // Iterate takes a function that takes 0 arguments

        // and returns an instance of an 'a. Iterate

        // itself return nothing (unit).

   

// Concrete type that implements the IList interface using integers

type IntList() =

    interface IList<int> with

        member this.AddElement(element) = ()

        member this.RemoveElement(element) = true

        member this.FindElement(element) = 123

        member this.Iterate f = ()

To call an interface member, you must do so via the interface itself – having access to an object instance that implements the interface is not enough. This means that when you have an object instance and want to call one of its interface members, you need to cast.  Luckily, that is the topic of the next section.[3]

Casting and Conversions

I like the explanation of casting from the MSDN documentation:

Conversion between types in an object hierarchy is fundamental to OOP. There are two basic types of conversions: casting up (upcasting) and casting down (downcasting). Casting up a hierarchy means casting from a derived object reference to a base object reference. Such a cast is guaranteed to work as long as the base class is in the inheritance hierarchy of the derived class. Casting down a hierarchy, from a base object reference to a derived object reference, succeeds only if the object actually is an instance of the correct destination (derived) type or a type derived from the destination type.

Casting comes into play in various situations, e.g., when converting numeric types, etc. We now look at casting from the perspective of objects and OOP, referred to as upcasting and downcasting.

Upcasting

Upcasting is the process of casting a derived object to an instance of a base object or implemented interface. To upcast, you use the upcast operator (:>). Since you cannot call an interface member directly through an object instance, you use the :> operator to obtain a proper interface reference. Let’s look at an example of calling the Send method defined in our previous IMessage example:

let email = new EMailMessage()

let sentOK = (email :> IMessage).Send()

The :> operator performs a static cast, which means that the success of the cast is determined at compile time. If a cast that uses :> compiles successfully, it is a valid cast and has no chance of failure at run time.

In F#, when you pass arguments to object methods, upcasting is applied automatically. For let-bound functions in a module[4], however, upcasting is not automatic. For let-bound functions in a module, you must use an explicit upcast, unless the parameter type is declared as a flexible type, which we discuss shortly.

The upcast operator can cast to any base class, regardless of how “far up” it is in the hierarchy. In the following example, we create a hierarchy and demonstrate an upcast up several levels of the ancestry:

type GrandPa() =

    // implied: inherit obj()

    member this.sayHello() = printfn "GrandPa says hello!"

type Dad() =

    inherit GrandPa()

    member this.sayHi() = printfn "Dad says hi!"

type Son() =

    inherit Dad()

    member this.sayGreetings() = printfn "Son says greetings!"

type Junior() =

    inherit Son()

    member this.sayGoodMorning() = printfn "Junior says 'morning!" 

    member this.sayHello() = printfn "Junior says hello!"   

 

let jr = new Junior()

jr.sayHello()

(jr :> GrandPa).sayHello()

Note that F# also supports an upcast keyword that uses the syntax upcast expression. F# tries to figure out, at compile time, the target of the upcast, i.e., what the expression should be upcast to. If it cannot figure out from context the target type, it reports a compile-time error.

Downcasting

The flip side of upcasting is, of course, downcasting. Downcasting is taking a base class object and casting it to an instance of a derived class. F# supports the downcast operator (:?>).

The :?> operator performs a dynamic cast, which means that the success of the cast is determined at run time.[5] If the object is compatible with the target type, the cast succeeds. If the object is not compatible with the target type, the runtime raises an InvalidCastException. The question mark comprising the operator reminds us that the cast could fail, i.e., it is “questionable.”

Like its OO brethren from other languages, the downcasting operator behaves as you’d expect when downcasting a base class to a derived class. If the base class instance is the result of a derived class having been upcast, the downcast works, e.g.:

let d = new Dad()

let g = (d :> GrandPa)  // upcast

let d2 = (g :?> Dad)    // related downcast works

On the other hand, if you attempt to cast a “pure” base class object (one not previously upcast) to a derived object, this results in an InvalidCastException, since F# cannot guarantee the fidelity of the cast, e.g.:

let g = new GrandPa()

let d = (g :?> Dad) // InvalidCastException

Due to the possible failure of the downcast operator, most F# code avoids it in favor of using the dynamic type checking operator, discussed next.

Pattern Matching and Dynamic Type Checking

Back in chapter 12, we discussed being able to pattern match on different data structures, and promised to revisit pattern matching with classes. Classes participate in pattern matching based not on their structure but on their types.

To determine the type of an instance, you use the type checking operator (:?). This operator is used in a match expression to test if a given instance is of a particualr type. To re-code the previous example using the :? operator, we would write the following:

let g = new GrandPa()

match g with

| :? Dad as gramps -> printfn "can downcast GrandPa to a Dad"

| _ -> ()

 

Now, this does not alter the “castability” of g - the match returns unit and does not print anything, meaning that as far as F# is concerned, the g instance cannot be downcast to a Dad instance. If we again perform the upcast followed by the downcast, the operation will succeed, as shown below:

let g = (new Dad() :> GrandPa)

match g with

| :? Dad as daddyo ->

    printfn "can cast GrandPa to a Dad"

    daddyo.sayHi()

| _ -> ()

Now the F# match expression “matches” and we get the "can downcast GrandPa to a Dad" and the "Dad says hi!" strings printed out to the F# Interactive Console.

This type of downcasting and pattern matching (type checking for classes) works all the way up and down the inheritance hierarchy. This means, for example, that you can cast a grandchild class to a grandparent base and subsequently downcast the grandparent, and all the casts work correctly.

Flexible Types

Flexible types are just a fancy name for an additional flavor of type constraint. Flexible types enable us to specify that a method accepts as an argument a type or anything derived from that type. Given their purpose, I think the name is misleading – they should really be called “flexible arguments.” These “flexible arguments” are specified via the hash symbol (#).

Why do we need flexible types? In most OO languages that support inheritance, there are situations where the language provides automatic upcasting – base classes, derived classes and interfaces can be used together somewhat seamlessly. In F#, these conversions are applied automatically to member arguments; however, they are not applied to “global” functions arguments defined via let. Here is what the the MSDN documentation says relative to upcasting and flexible types:

In many object-oriented languages, upcasting is implicit; in F#, the rules are slightly different. Upcasting is applied automatically when you pass arguments to methods on an object type. However, for let-bound functions in a module, upcasting is not automatic, unless the parameter type is declared as a flexible type.

In F#, as in other object-oriented languages, there are contexts in which derived types or types that implement interfaces are automatically converted to a base type or interface type. These automatic conversions occur in direct arguments, but not when the type is in a subordinate position, as part of a more complex type such as a return type of a function type, or as a type argument. Thus, the flexible type notation is primarily useful when the type you are applying it to is part of a more complex type.

What this all means is that flexible types provide a way to declare functions that accept as arguments values of a particular OO ancestry or interface family. In other words, flexible types are used when we want to say “function arguments of this type and anything derived from this type” and we want to take advantage of transparent upcasts.

Flexible types are useful specifically when the automatic conversion to types higher in the type hierarchy does not occur automatically, but you still want to enable your code to work with any type in the hierarchy or any type that implements an interface.

In the F# library, we tend to see flexible types in the definition of Seq  methods. For example, the Seq.append method boasts the following signature:

#seq<'a> -> #seq<'a> -> seq<'a>

Note that this signature does not define a new type – it simply annotates an existing type to tell the compiler to accept any type in the specified hierarchy. In this particular example, since seq represents an IEnumerable, the Seq.append method accepts an IEnumerable or any interface or class implementing IEnumerable.

It is helpful to understand that a flexible type is equivalent to a generic type that has a type constraint (:>) applied.  For example:[6]

open System.Windows.Forms

let
setTextOfControl (c: #Control) (s: string) = c.Text <- s

// is equivalent to
let setTextOfControl2 (c: 'a when 'a :> Control) (s: string) = c.Text <- s

Boxing

An idea that goes hand-in-hand with casting and conversions is that of boxing and unboxing. Boxing is the process of wrapping an object around a value type, making it useful as a reference type (like putting the value type in a reference type “box”). Unboxing is the process of unwrapping the value type (taking it “out of the box.”) This can be useful , for instance, when dealing with legacy containers that do not support generic type specifications. The following example shows an example of boxing and unboxing:

let myInt = box 100

let i : int = unbox myInt

Using boxing and unboxing, you can store value types in collections and other data structures that accept object (obj in F#) parameters. This is largely a thing of the past now that .NET fully supports generics; however, it does come up from time to time in dealing with legacy code.

Type Extensions

Type extensions enable you to add members to a type, even when you do not have direct access to the type’s source or cannot inherit from the type, e.g., when you already inherit from another base class.

F# supports two forms of type extensions called intrinsic extensions and optional extensions. Intrinsic extensions appear in the same namespace or module, source file and assembly as the type being extended. Optional extensions do not.

Intrinsic Extensions

To create an intrinsic extension, you use the following syntax:

// Intrinsic extension
type typename with
    [ static ] member self-identifier.member-name =
        body
        ...
    [ end ]

Here, typename is the type being extended. The members (properties or methods) that you add are “hooked onto” this type and act like built-in members. These members may be static members or instance members. Clients do not see a difference between indigenous and intrinsically-extended members. The only restriction is that extension methods cannot be virtual, a.k.a, abstract. In the following example, we define a Message class that we then extend using intrinsic extension.

// Orignal type

type Message() =

    member this.Send(recipient: string, body: string) =

        printfn "Send message to %s" recipient

 

// The author of type forgot to add an Attach member. Let's

// do this with an intrinsic extension.

type Message with

    member this.Attach(entity) =

        // attach entity to message

        true

 

// Clients can use both methods

let m = new Message()

let attached = m.Attach("attachment here")

m.Send("joe@smith.com", "hey there!")

The key to defining intrinsic extensions lies in the original class name, e.g., Message, paired with the keyword with. Once available, clients call intrinsic extension methods exactly like they call any other methods defined on the class.

Optional Extensions

An optional extension is one that appears outside of the original module, namespace or assembly of the type being extended. We generally use optional extensions we don’t have access to the source code of the original type, and don’t want to or can’t inherit.

While intrinsic extensions appear in Intellisense (they are available to reflection), optional extensions do not (they are not available to reflection). Optional extensions must be in modules, and they are in scope only when the module in which they are defined is open. To create an optional extension, we use the following syntax:

// Optional extension
type fully-qualified-typename with
    member self-identifier.member-name =
        body
        ...
    [ end ]

To show how this works, let create an extension to the String class that returns true if the string is a palindrome or false otherwise. Note that I’ve chosen to make this member static for illustrative purposes. Although the current MSDN documentation states that optional extensions are compiled to static members, they appear not to function that way – you can declare them as static or non-static, and they behave accordingly.

// How to create an optional extension

open System

open System.Text

 

type System.String with

    static member IsPalindrome(s: string) =

        let l = s.ToLower()

        let b = new StringBuilder()

        for i = s.Length - 1 downto 0 do

            b.Append(l.[i])

        b.ToString() = l

Object Expressions

An object expression is an F# expression that creates a new type “on the fly”. The new type created is anonymous, meaning it has no accessible name, and must be based on an existing base type, interface or set of interfaces. Object expressions combine the best of lambda functions and component-based programming.

We generally use object expressions to create types that are used locally and that participate in specific situations. For example, if you’ve ever used LINQ to read a database and project the returned data into an anonymous type, you have realized the utility of object expressions.

The canonical example used for object expressions is that of object comparison with an eye towards sorting. I think it’s a useful example as it captures the essence of object expressions and demonstrates their use in a few lines of code. For this example[7], we’ll create a record called Employee and use object expressions to generate classes that can sort Employees based on different criteria.

open System.Collections.Generic

 

type Employee = { lastname: string; salary: float }

let employees = [|

                    {lastname="Smith"; salary=60000.00};

                    {lastname="Jones"; salary=80000.00};

                    {lastname="Adams"; salary=99000.00};

                |]

                   

let sortEmployees emps (comparer: IComparer<Employee>) =

    System.Array.Sort(emps, comparer)

    emps |> Seq.iter (fun e -> printf "(%s, $%0.2f) " e.lastname e.salary)
    printfn ""


sortEmployees employees {
    new IComparer<Employee> with member this.Compare(x, y) =  

        x.lastname.CompareTo(y.lastname) }

sortEmployees employees {
    new IComparer<Employee> with member this.Compare(x, y) = 
        x.salary.CompareTo(y.salary) }

In addition to having the ability to implement interfaces, as just shown, you can use object expressions to inherit from existing classes and override abstract properties and methods as needed. Note that you cannot use object expressions to augment or change a type that is sealed.

In the following example, we use an object expression to override a base class method:

type GrandPa() =

    abstract member WalkThisWay: unit -> unit

    default this.WalkThisWay() =

        printfn "GrandPa walks slowly"

 

let Walk(person: GrandPa) =

    person.WalkThisWay()

 

Walk { new GrandPa() with member x.WalkThisWay() = printfn "I'm walking quickly!" }

The Walk function is passed an instance of a new class, one that inherits from GrandPa and overrides its WalkThisWay virtual member.

In general, object expressions are used primarily to supply dynamic implementations of a single interface; however, as shown, you can use them to selectively override class members as well.

The syntax for defining object expressions is as follows:

// When typename is a class
{ new typename [type-params] arguments with
    member-definitions
    [ additional-interface-definitions ]
}

// When typename is not a class
{ new typename [generic-type-args] with
    member-definitions
    [ additional-interface-definitions ]
}

Here, typename represents an existing class or interface type. type-params describes optional generic type parameters. The arguments are used only for class types that require constructor parameters. The member-definitions are overrides of base class methods, or implementations of abstract methods from either a base class or an interface.

Custom EventArgs

In Chapter 13, we discussed events as members of classes. As part of this discussion, we mentioned that events handlers generally work with EventArgs subclasses. Now that we understand inheritance, it’s simple to create custom EventArgs derivatives. In the following example, we create an event source, Alarm, an event sink, Guardian, and a custom EventArgs-derived class that is passed from the Alarm to the Guardian when the Alarm triggers.

// Defines EventArgs

open System    

 

// Custom EventArgs

type AlarmDetails(msg: string) =

    inherit EventArgs()

    member this.Message = msg

 

// Event source   

type Alarm() =

    let triggerAlarmEvent, alarmEvent = Event.create()

    member this.OnAlarmTriggered = alarmEvent

    member this.TriggerAlarm(details: AlarmDetails) = 

        triggerAlarmEvent(details) // custom EventArgs passed to handlers

 

// Event sink - receives AlarmDetails

type Guardian(a: Alarm) =

    do a.OnAlarmTriggered.Add(

        fun details -> printfn "*** ALARM! %s ***" details.Message)

 

// Test

let a = new Alarm()

let g = new Guardian(a)

a.TriggerAlarm(new AlarmDetails("Intruder detected on Level 7!"))

Your custom EventArgs classes can be as sophisticated as you’d like. They should carry enough information so that handlers can use them to intelligently respond to events in an efficient and effective manner.

Custom Delegates

Events are built on top of .NET delegates. Delegates are objects that “wrap” or “point to” methods – they are “function objects” so to speak. Clients can invoke the functions that the delegates wrap. In F#, since we can pass functions around as first-class citizens, we can largely forgo using delegates; however, F# supports delegates to interact and interoperate with code written in other .NET libraries and languages. This includes participating in cross-language events, e.g., the ability to raise an event in F# that is subsequently handled in C# and vice versa.

In most cases, you don’t need to interact with delegates directly, e.g., events hide the fact that you’re using delegates; however, there may be circumstances where you need to wrap a function outside the context of events. For example, if you are using a library written in C# and need to provide a callback function to one of its API methods, you could wrap your F# callback function with a delegate and hand that to the C# API.

A delegate has the same signature as the function it wraps. In F#, we define delegates using the following construction:

type delegate-typename = delegate of type1 -> type2

With the above syntax in mind, let’s suppose we have a simple F# function that takes a string argument and returns an integer, as shown below:

let countDigits (s: System.String) =

    [for x in s do if Char.IsDigit(x) then yield x].Length

To create a delegate that wraps this function, we would write the following:

type CountDigitsDelegate = delegate of System.String -> int

This is a delegate that wraps a particular type of function – one that takes a System.String and returns an int. To use this delegate, we need to create an instance of it, wrap the target function, and then invoke the function (through the delegate). This is exactly what this sample code does:

let d = new CountDigitsDelegate(countDigits)    // wraps target function

let count = d.Invoke("hell0 w0r1d")             // invoke target function

Note the use of the Invoke method on the delegate instance. Invoke is used to call the method wrapped by the delegate.

If the target method takes more than one argument, we need to use a tuple to describe the argument types. In this next example, the CalcRaise function accepts 3 arguments: the last name of an employee (string), the years of service (int), and a current salary (float):

let calcRaise(name: string, yearsOfService: int, currentSalary: float) =

    currentSalary * (1.0 + (float yearsOfService / 15.0))

 

type CalcDelegate = delegate of (string * int * float) -> float

Calling the calcRaise function is as simple as invoking it via the delegate’s Invoke method:

let c = new CalcDelegate(calcRaise)

let newSalary = c.Invoke("smith", 5, 45000.0)

The above definitions of delegates are no more than syntactic sugar on top of the .NET System.Delegate and System.MulticastDelegate types. As such, the delegate instance is a full-fledged single- and multi-cast .NET delegate object.

Note that delegates are capable of wrapping static functions, non-static functions, static members of types, and non-static members of types. In other words, you can pretty much call anything via a delegate.

There is more to delegates that go beyond the scope of this “survival” text, including the ability to combine delegates and to work with them asynchronously. Please consult the MSDN documentation on delegates for a full treatment of this subject.

Attributes

Attributes in .NET are classes that enable us to attach metadata and code to other structures such as classes, functions, properties, etc. They are compile-time classes, but you should think of them as annotations - notes to the compiler that are attached to things in your source code to qualify or change how the compiler or run-time views those things.

Attributes are available at runtime to reflection logic and the .NET runtime recognizes certain, well-known attributes, which affect the way it will interact with the decorated program constructs. Some examples of well-known attributes include declarative transactions, serialization control, and involved with COM interop.

Applying Attributes

In F#, attributes can be applied to functions, methods, assemblies, modules, types (classes, records, structures, interfaces, delegates, enumerations, unions, etc.), constructors, properties, fields, events, parameters, type parameters, and return values. Attributes are not allowed on let bindings inside classes, expressions, or workflow expressions.

To apply an attribute to an F# construct, we use the following syntax:

[<target:attribute-name(arguments)>]

Here, target represents the kind of programming construct that the attribute applies to. It is optional and can include assembly, module, return, field, property, param, type, and event. If omitted, the attribute is assumed to apply to the next construct appearing in lexical order.

attribute-name is the name of the attribute being used, e.g., DebuggerDisplayAttribute. Note that by convention, the suffix Attribute is generally used in attribute type names; however, in many contexts, this suffix is omitted and the shorter form is used, e.g., DebuggerDisplay.

 Since an attribute is a class, it can have a constructor. The arguments shown in the above syntax are those passed to the attribute’s constructor. Of course, if the attribute has a default constructor, there’s no need to pass arguments. F# attributes support both positional arguments as well as named arguments. Named arguments, if used, must follow positional arguments.

In the following example, we apply the DebuggerDisplayAttribute to a custom Movie type that we’ve defined:

open System.Diagnostics

 

[<DebuggerDisplay("{Marquee}")>]    // Marquee is assumed to be a property or method.

type Movie() =                      // The debugger will display Marquee's value.

    let title = "Some Movie"

    let director = "Talented Person"

    member this.Marquee = "*** " + title + " by " + director + " ***"

Because we’ve omitted the target type in the attribute declaration, F# applies it to the type Movie (the element following the attribute lexically). If DebuggerDisplay were restricted to applying to other constructs, the F# compiler would report an error.

Creating Custom Attributes

In addition to the set of well-known .NET attributes we can use, we have the ability to add our own custom attributes to the mix, and attach arbitrary metadata and code to our classes, properties, functions, etc. The .NET runtime does nothing with our custom attributes per se. They are useful for attaching arbitrary information to constructs that our own software might want to interrogate and use.[8] To read attribute data at runtime, your application uses the standard .NET Reflection APIs.

Up to this point, we’ve interacted with attributes ala applying existing ones to our types using the [<attribute>] syntax. Now it’s time to discuss how to create our own attributes, given that we’re well-versed in type construction and inheritance. Note that there is nothing special about creating attributes in F# vs. other .NET languages.

To define a .NET attribute, you need to do a handful of things:

·         Define and build an attribute class and specify what kinds of constructs the attribute applies to

·         Apply the attribute class to the target

·         Optionally use the .NET Reflection APIs to read the attribute information at runtime

Let’s look at each of these steps in brief here. For all the nitty-gritty details of attribute development, see the MSDN documentation.

Defining an Attribute Class and It’s Usage

To define an attribute class, you define a new type that inherits from System.Attribute. For example, let’s create an attribute that we can use to restrict method invocation to certain business roles:

[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Property, AllowMultiple=true)>]

type SecureMemberAttribute(role: string) =

    inherit Attribute()

    let _role = role   

We use the AttributeUsage class to define the kinds of programming constructs to which this attribute applies. In this example, we’re limiting SecureMemberAttribute’s usage to methods and properties. AllowMultiple=true means that we can apply this attribute multiple time to any given element. We may want to do this so that we can apply multiple roles to the list of valid roles, for example. Of course, there are other ways to solve this problem, e.g., by having the attribute class accept an array of strings; however, I just wanted to show the possibilities here.

Applying an Attribute

Now that we have a new attribute, we can apply it. In the following example, we create a BankAccount class, one of whose methods we decorate with our new attribute:

type BankAccount(balance: decimal) =

    let mutable _balance = balance

    member this.Deposit(amount) =

        _balance <- _balance + amount

    member this.Withdraw(amount) =

        _balance <- _balance - amount

       

    [<SecureMember("Manager"); SecureMember("Supervisor")>]

    member this.Transfer(fromAcct: string, toAcct: string, amount: decimal) =

        // Secure function: transfer funds

Note the multiple application of the attribute.

Now, the .NET libraries have no idea what to do with our custom attribute. The runtime will not prevent the calling of the BankAccount’s Transfer method. To actually use the SecureMember attribute, your code would need to use Reflection APIs to extract the attribute class and leverage it accordingly.

What You Need to Know

·         F# supports class inheritance. A class can have at most one base class, although it can additionally support multiple interfaces.

·         You upcast using the :> operator and downcast using the :?> operator.

·          You use the :? operator in match expressions to test the type of a class.

·         In F#, virtual members are defined using the abstract keyword. F# does not have a keyword virtual.

·         F# supports abstract classes, which can include default member implementations.

·         F# supports interfaces, which cannot include default member implementations.

·         Interfaces can include generic type parameters and participate in inheritance.

·         You can add extensions to existing classes. Extensions come in two flavors: intrinsic, where the extensions are defined in the module of the extended class, and optional, where the extensions are defined outside the context of the module.

·         Anonymous classes can be created on-the-fly in object expressions.

·         You can use and create custom .NET attributes in F#.

 

 

 



[1] However, you can provide a default implementation by also including a separate definition of the member as a method together with the default keyword. Doing so is equivalent to creating a virtual base class method in other .NET languages. Such a virtual method can be overridden in classes that implement the interface.

[2] Interfaces form the foundation of component-based programming, which is arguably a more robust way to develop systems vs. over-using inheritance. The philosophies governing this argument are beyond the scope of this book.

[3][3] OK, luck didn’t have much to do with this.

[4] This simply means global functions defined in a module via the let keyword, e.g., let f x = x * x.

[5] As a parallel to the upcast expression construct, F# supports the dynamic downcast expression. If the compiler cannot infer a specific target type from context, it reports an error.

[6] Example taken from Don Syme’s Expert F# book.

[7] This example was adapted from the F# Wikibook.

 

[8] Attributes are used often in frameworks that implement dependency injection and aspect-oriented development models.

 

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...