The Crafting Strider

Raman But-Husaim’s personal webblog. Ideas, tech articles born through the life of a software engineer

Bank Account task on Exercism in F#

Functional programming was always an interesting topic for me. I’ve firstly seen it in reality at the university, where we had a very basic and confusing course for a single semester. As part of this course, we’ve tried to study the general concepts and Clojure as a programming language. I could not say that it gave me a lot, but at least I’ve tried to grasp something outside of classic OOP.

There were few attempts on my own afterwards - some F#, then OCaml, Haskell, next quite a long journey with Erlang, Elixir and the fantastic BEAM VM, and now I’ve returned to F# as a strongly typed language working on a VM that I’m pretty familiar with - dotnet. As the best way of learning something is to use in practice, I’ve referred to Exercism to get a set of simple yet educational tasks to get familiar with the concepts and the language.

I’ve solved a bunch of exercises and at some point reached Bank Account task. Even though the exercise itself is pretty straightforward, there is one important piece inside:

Create an account that can be accessed from multiple threads/processes (terminology depends on your programming language).

“Ok, this is where concurrency and multithreading comes into play” was my initial thought. dotnet as platform has a plenty of thread synchronisation primitives available, such as Monitor and the helper lock statement, Interlocked, Mutex, Semaphore and many others. I’m a big fan of BEAM VM and Actor Model. Even though BEAM does not implement the actor model, it relies on a message-based approach to concurrency (as actors). I remembered that there was some form of actor model in F# library as well. I’ve started the search and found an awesome article written by Scott Wlaschin. The article contains a lot of useful information about the concepts and concrete implementation in F#.

F# library has a class called MailboxProcessor that represents some form of an actor or agent. In essence this is an in-memory message queue that could receive messages from multiple threads, however the actual processing happens on a single thread. I.e., it is heavily inspired by Erlang even though much, much simpler.

I went through a series of articles - here, here and here to become more familiar with the concept. MailboxProcessor has all the required methods for the task - Post to send a message asynchronously and PostAndReply - to send a message and receive a response.

The missing point for me was how this agent should be modelled from the code perspective in order to encapsulate its message-based behaviour, i.e. model a BankAccount entity. In Elixir a set of functions was defined (functional interface) around the GenServer to simplify working with it. Class in F# is an answer that helps to achieve this functional interface and hide the collaboration with the agent. Each operation with an entity (BankAccount) should rely on a certain type of message, and discriminated unions and pattern-matching in F# are the answer to this. As a side not, Elixir relies on a similar mechanism, even though it is a dynamic language.

At the end of the day, the piece of code looked like this (WARNING SPOILER):

module BankAccount

type Agent<'T> = MailboxProcessor<'T>

type BankAccountOperation =
    | Open
    | Close
    | GetBalance of AsyncReplyChannel<decimal option>
    | UpdateBalance of decimal

type BankAccountStatus =
    | Active
    | Inactive

type BankAccountState =
    { Status: BankAccountStatus
    Balance: decimal option }

type BankAccount() =

    let mutable state = { Status = Inactive; Balance = None }

    let openAccount () =
        state <- { Status = Active; Balance = Some 0.0m }

    let closeAccount () =
        state <- { Status = Inactive; Balance = None }

    let getBalance () = state.Balance

    let updateBalance amount =
        match state.Status with
        | Active ->
            let balance = getBalance () |> Option.get
            let newBalance = balance + amount
            state <- { state with Balance = Some newBalance }
        | _ -> ()

    let agent =
        Agent.Start(fun inbox ->
            let rec messageLoop () =
                async {
                    let! msg = inbox.Receive()

                    match msg with
                    | Open -> openAccount ()
                    | Close -> closeAccount ()
                    | GetBalance replyChannel ->
                      let balance = getBalance ()
                      replyChannel.Reply balance
                    | UpdateBalance amount ->
                        updateBalance amount

                    return! messageLoop ()
                }
            messageLoop ())

    member _.Open() = agent.Post Open

    member _.Close() = agent.Post Close

    member _.GetBalance() =
        agent.PostAndReply(
            (fun replyChannel -> GetBalance replyChannel), 10000)

    member _.UpdateBalance amount =
        agent.Post(UpdateBalance amount)

let mkBankAccount () = BankAccount()

let openAccount (account: BankAccount) =
    account.Open()
    account

let closeAccount (account: BankAccount) =
    account.Close()
    account

let getBalance (account: BankAccount) = account.GetBalance()

let updateBalance (change: decimal) (account: BankAccount) =
    account.UpdateBalance change
    account