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:

module FizzBuzzExample

Class Types

Now we are going to create our first class type:

type FizzBuzz() =
    member _.Calculate(value) =
        [(3, "Fizz");(5, "Buzz")]
        |> List.map (fun (v, s) -> if value % v = 0 then s else "")
        |> List.reduce (+)
        |> fun s -> if s <> "" then s else string value

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.

let doFizzBuzz =
    let fizzBuzz = FizzBuzz()
    [1..15]
    |> List.map fizzBuzz.Calculate

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;

type FizzBuzz(mapping) =
    member _.Calculate(value) = 
        mapping
        |> List.map (fun (v, s) -> if n % v = 0 then s else "")
        |> List.reduce (+)
        |> fun s -> if s <> "" then s else string n

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:

let doFizzBuzz =
    let fizzBuzz = FizzBuzz([(3, "Fizz");(5, "Buzz")])
    [1..15]
    |> List.map fizzBuzz.Calculate

We can move the function code from the member into the body of the class type as a new inner function:

type FizzBuzz(mapping) =
    let calculate n =
        mapping
        |> List.map (fun (v, s) -> if n % v = 0 then s else "")
        |> List.reduce (+)
        |> fun s -> if s <> "" then s else string n

    member _.Calculate(value) = calculate value

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:

type IFizzBuzz =
    abstract member Calculate : int -> string

Now we need to implement the interface in our FizzBuzz class type:

type FizzBuzz(mapping) =
    let calculate n =
        mapping
        |> List.map (fun (v, s) -> if n % v = 0 then s else "")
        |> List.reduce (+)
        |> fun s -> if s <> "" then s else string n

    interface IFizzBuzz with
        member _.Calculate(value) = calculate value

Nice and easy but you will see that we have a problem; The compiler has highlighted the Calculate function call in our doFizzBuzz function.

let doFizzBuzz =
    let fizzBuzz = FizzBuzz([(3, "Fizz");(5, "Buzz")])
    [1..15]
    |> List.map (fun n -> fizzBuzz.Calculate(n)) // Problem

There is an error because F# does not support implicit casting, so we have to upcast the instance to IFizzBuzz:

let doFizzBuzz =
    let fizzBuzz = FizzBuzz([(3, "Fizz");(5, "Buzz")]) :> IFizzBuzz //Upcast  
    [1..15]
    |> List.map (fun n -> fizzBuzz.Calculate(n)) // Fixed

An alternative would be to upcast as you use the interface function:

let doFizzBuzz =
    let fizzBuzz = FizzBuzz([(3, "Fizz");(5, "Buzz")])
    [1..15]
    |> List.map (fun n -> (fizzBuzz :> IFizzBuzz).Calculate(n))

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:

type IRecentlyUsedList =
    abstract member IsEmpty : bool
    abstract member Size : int
    abstract member Clear : unit -> unit
    abstract member Add : string -> unit
    abstract member Get : int -> string option

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:

type RecentlyUsedList() =
    let items = ResizeArray()

    let add item =
        items.Remove item |> ignore
        items.Add item 

    let get index =
        if index >= 0 && index < items.Count 
        then Some items.[items.Count - index - 1]
        else None

    interface IRecentlyUsedList with
        member _.IsEmpty = items.Count = 0
        member _.Size = items.Count
        member _.Clear() = items.Clear()
        member _.Add(item) = add item
        member _.Get(index) = get index

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):

let mrul = RecentlyUsedList() :> IRecentlyUsedList

mrul.Add "Test"

mrul.IsEmpty = false // Should return true

mrul.Add "Test2"
mrul.Add "Test3"
mrul.Add "Test"

mrul.Get(0) = Some "Test" // Should return true

Now let's add a maximum size (capacity) to our IRecentlyUsedList interface:

type IRecentlyUsedList =
    abstract member IsEmpty : bool
    abstract member Size : int
    abstract member Capacity : int
    abstract member Clear : unit -> unit
    abstract member Add : string -> unit
    abstract member Get : int -> string option

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:

type RecentlyUsedList(capacity:int) =
    let items = ResizeArray(capacity)

    let add item =
        items.Remove item |> ignore
        if items.Count = items.Capacity then items.RemoveAt 0
        items.Add item

    let get index =
        if index >= 0 && index < items.Count 
        then Some items.[items.Count - index - 1]
        else None

    interface IRecentlyUsedList with
        member _.IsEmpty = items.Count = 0
        member _.Size = items.Count
        member _.Capacity = items.Capacity
        member _.Clear() = items.Clear()
        member _.Add(item) = add item
        member _.Get(index) = get index

All done. Let's test a recently used list with capacity of 5 using FSI:

let mrul = RecentlyUsedList(5) :> IRecentlyUsedList

mrul.Capacity // Should be 5

mrul.Add "Test"
mrul.Size // Should be 1
mrul.Capacity // Should be 5

mrul.Add "Test2"
mrul.Add "Test3"
mrul.Add "Test4"
mrul.Add "Test5"
mrul.Add "Test6"
mrul.Add "Test7"
mrul.Add "Test"

mrul.Size // Should be 5
mrul.Capacity // Should be 5
mrul.Get(0) = Some "Test" // Should return true

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:

type Coordinate(latitude: float, longitude: float) =
    member _.Latitude = latitude
    member _.Longitude = longitude

To test equality, we can write some simple checks we can run in FSI:

let c1 = Coordinate(25.0, 11.98)
let c2 = Coordinate(25.0, 11.98)
let c3 = c1
c1 = c2 // false
c1 = c3 // true - reference the same instance

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:

open System

[]
type GpsCoordinate(latitude: float, longitude: float) =
    let equals (other: GpsCoordinate) =
        if isNull other then
            false
        else
            latitude = other.Latitude
            && longitude = other.Longitude

    member _.Latitude = latitude
    member _.Longitude = longitude

    override this.GetHashCode() =
        hash (this.Latitude, this.Longitude)

    override _.Equals(obj) =
        match obj with
        | :? GpsCoordinate as other -> equals other
        | _ -> false

    interface IEquatable with
        member _.Equals(other: GpsCoordinate) =
            equals other

    static member op_Equality(this: GpsCoordinate, other: GpsCoordinate) =
        this.Equals(other)

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:

let c1 = Coordinate(25.0, 11.98)
let c2 = Coordinate(25.0, 11.98)
c1 = c2 // true

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.

Blog 7/12/23

Introduction to Functional Programming in F# – Part 11

Learn type inference and generic functions in F#. Boost efficiency and flexibility in your code with these essential programming concepts.

Blog 10/1/22

Introduction to Functional Programming in F# – Part 4

Unlock F# collections and pipelines. Manage data efficiently and streamline your functional programming workflow with these powerful tools.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 7

Explore LINQ and query expressions in F#. Simplify data manipulation and enhance your functional programming skills with this guide.

Blog 10/11/22

Introduction to Functional Programming in F# – Part 5

Master F# asynchronous workflows and parallelism. Enhance application performance with advanced functional programming techniques.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 8

Discover Units of Measure and Type Providers in F#. Enhance data management and type safety in your applications with these powerful tools.

Blog 5/18/22

Introduction to Functional Programming in F#

Dive into functional programming with F# in our introductory series. Learn how to solve real business problems using F#'s functional programming features. This first part covers setting up your environment, basic F# syntax, and implementing a simple use case. Perfect for developers looking to enhance their skills in functional programming.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 6

Learn error handling in F# with option types. Improve code reliability using F#'s powerful error-handling techniques.

Blog 9/15/22

Introduction to Functional Programming in F# – Part 3

Dive into F# data structures and pattern matching. Simplify code and enhance functionality with these powerful features.

Blog 8/8/23

Introduction to Functional Programming in F# – Part 12

Explore reflection and meta-programming in F#. Learn how to dynamically manipulate code and enhance flexibility with advanced techniques.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 9

Explore Active Patterns and Computation Expressions in F#. Enhance code clarity and functionality with these advanced techniques.

Blog 9/13/22

Introduction to Functional Programming in F# – Part 2

Explore functions, types, and modules in F#. Enhance your skills with practical examples and insights in this detailed guide.

Blog 11/30/22

Introduction to Partial Function Application in F#

Partial Function Application is one of the core functional programming concepts that everyone should understand as it is widely used in most F# codebases.In this post I will introduce you to the grace and power of partial application. We will start with tupled arguments that most devs will recognise and then move onto curried arguments that allow us to use partial application.

Blog 3/11/21

Introduction to Web Programming in F# with Giraffe – Part 2

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/10/21

Introduction to Web Programming in F# with Giraffe – Part 1

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/12/21

Introduction to Web Programming in F# with Giraffe – Part 3

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 7/21/20

Understanding F# applicatives and custom operators

In this post, Jonathan Channon, a newcomer to F#, discusses how he learnt about a slightly more advanced functional concept — Applicatives.

Blog 8/7/20

Understanding F# Type Aliases

In this post, we discuss the difference between F# types and aliases that from a glance may appear to be the same thing.

Blog 11/27/23

Part 4: Save Time and Analyze the Database File

ChatGPT-4 enables you to analyze database contents with just two simple steps (copy and paste), facilitating well-informed decision-making.

Blog 9/27/22

Creating solutions and projects in VS code

In this post we are going to create a new Solution containing an F# console project and a test project using the dotnet CLI in Visual Studio Code.

News 11/4/24

EverIT becomes part of catworkx and TIMETOACT GROUP

Cologne/Budapest, 4 November 2024 – catworkx (part of TIMETOACT GROUP), a leading partner for Enterprise integration based on the Atlassian platform, is acquiring EverIT, a specialized Hungarian based Atlassian Partner. Together, the companies will build on their long-standing relationship and expand catworkx’s leading market position into Central Eastern Europe and strengthen catworkx’s global offering. The parties agreed not to disclose the details of the transaction.

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!