Introduction to Functional Programming in F# – Part 10
Introduction
In this post we are going to see how we can utilise some of the object programming features that F# offers. F# is a functional-first language but sometimes it is beneficial to use objects, particularly when interacting with code written in other, less functional, .Net languages or when you want to encapsulate some internal data structures and/or mutable state.
F# can do pretty much anything that C#/VB.Net can do with objects. We are going to concentrate on the core object programming features; class types, interfaces, encapsulation and equality.
Setting Up
Open VSCode and add a new folder. Add three new files (FizzBuzz.fs, RecentlyUsedList.fs and Coordinate.fs).
Solving the Problem
We will start in fizzbuzz.fs where we will be implementing FizzBuzz using object programming.
Add a module to the top of the page:
Class Types
Now we are going to create our first class type:
Points of interest:
The brackets () after the type name are required. They can contain argument as we will see soon.
The member keyword defines the accessible members of the type.
The underscore is just a placeholder - it can be anything. It is convention to use _, __ or this.
Now that we have created our class type, we need to instantiate it to use it.
We don't use 'new' to create an instance of FizzBuzz. You only use 'new' when using an object that implements IDisposible and then we would use 'use' instead of 'let' to create a code block.
At the moment, we can only use '[(3, "Fizz");(5, "Buzz")]' as the mapping but it is easy to pass the mapping in through the constructor;
Notice that we don't need to assign the constructor argument to a binding to use it.
Now we need to pass the mapping in as the argument to the constructor in the doFizzBuzz function:
We can move the function code from the member into the body of the class type as a new inner function:
You cannot access the new calculate function from outside the class type. You don't have to do this but I find that it makes the code easier to read, especially as the number of class members increases.
Interfaces
Interfaces are very important in object programming as they define a contract that an implementation must offer. Let's add an interface to our fizzbuzz example:
Now we need to implement the interface in our FizzBuzz class type:
Nice and easy but you will see that we have a problem; The compiler has highlighted the Calculate function call in our doFizzBuzz function.
There is an error because F# does not support implicit casting, so we have to upcast the instance to IFizzBuzz:
An alternative would be to upcast as you use the interface function:
Just because you can, it doesn't mean you should
The code above is designed to show how to construct class types and use interfaces. If you find yourself constructing interfaces for single functions, ask yourself if you really, really need the extra code and complexity or whether a simple function is enough.
Next we move on to a more complex/realistic example - a recently used list.
Encapsulation
We are going to create a recently used list as a class type. We will encapsulate a mutable collection within the type and provide an interface for how we can interact with it. The recently used list is an ordered list with the most recent item first but it is also a set as each value can only appear once in the list.
First we need to create an interface in RecentlyUsedList.fs:
By looking at the signatures, we can see that IsEmpty and Size are read-only properties and Clear, Add and Get are functions. Now we can create our class type and implement the IRecentlyUsedList interface:
A ResizeArray is the F# synonym for a standard .Net mutable generic list. Encapsulation ensures that you cannot access it directly (unless you use reflection etc) via the public interface.
Let's test our code in FSI (run each line seperately):
Now let's add a maximum size (capacity) to our IRecentlyUsedList interface:
You will notice that the compiler is complaining that we haven't implemented all of the interface, so let's fix it, add the capacity as a constructor argument, and add code to the Add function to ensure the oldest item is removed if we are at capacity when adding a new item:
All done. Let's test a recently used list with capacity of 5 using FSI:
Encaspulation inside class types works really nicely. Now we move on to the issue of equality. Most of the types in F# support structural equality but class tyes do not.
Equality
Let's create a simple class type to store a GPS Coordinate in Coordinate.fs:
To test equality, we can write some simple checks we can run in FSI:
To support non-referential equality, we need to override GetHashCode and Equals, implement IEquatable and if we are going to use it with other .Net laguages, we need to handle = using op_Equality and allow null literals. This is an example of what we need:
We have used two built-in functions (hash and isNull) and we use pattern matching in Equals(obj) to see if the obj can be cast as a GpsCoordinate.
If we test this, we get the expected equality:
We have only scratched the surface of what is possible with F# object programming. We will look at other features in a future post. If you want to find out more about this topic (plus many other useful things), I highly recommend that you read Stylish F# by Kit Eason.
Conclusion
In this post we have started to look at object programming in F#. In particular, we have looked at scoping/visibility, encapsulation, interfaces and equality. The more you interact with the rest of the .Net ecosystem, the more you will need to use object programming.
In the next post we will primarily look at recursion.
If you have any comments on this series of posts or suggestions for new ones, send me a tweet (@ijrussell) and let me know.