Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with classes in F# (mutability vs immutability / members vs free functions)

Tags:

f#

I am currently doing the exercism.io F# track. For everyone who doesn't know it, it's solving small problems TDD style to learn or improve a programming language.

The last two tasks were about the usage of classes in F# (or types as they are called in F#). One of the tasks uses a BankAccount that has a balance and a status (open/closed) and can be altered by using functions. The usage was like this (Taken from the test code):

let test () =
    let account = mkBankAccount () |> openAccount
    Assert.That(getBalance account, Is.EqualTo(Some 0.0)

I wrote the code that makes the test pass using an immutable BankAccount class that can be interacted with using free functions:

type AccountStatus = Open | Closed

type BankAccount (balance, status) =
    member acc.balance = balance
    member acc.status = status

let mkBankAccount () =
    BankAccount (0.0, Closed)

let getBalance (acc: BankAccount) =
    match acc.status with
    | Open -> Some(acc.balance)
    | Closed -> None

let updateBalance balance (acc: BankAccount) =
    match acc.status with
    | Open -> BankAccount (acc.balance + balance, Open)
    | Closed -> failwith "Account is closed!"

let openAccount (acc: BankAccount) =
    BankAccount (acc.balance, Open)

let closeAccount (acc: BankAccount) =
    BankAccount (acc.balance, Closed)

Having done a lot of OO before starting to learn F# this one got me wondering. How do more experienced F# developers use classes? To make answering this question more simple, here are my main concerns about classes/types in F#:

  • Is the use of classes in a typical OO fashion discouraged in F#?
  • Are immutable classes preferred? ( I found them to be confusing in the above example)
  • What is the preferred way to access/alter class data in F#? (Class member functions and get/set or free functions which allow piping? What about static members to allow piping and providing the functions with a fitting namespace?)

I'm sorry if the question is vague. I don't want to develop bad coding habits in my functional code and i need a starting point on what good practices are.

like image 722
Luca Fülbier Avatar asked Aug 12 '16 17:08

Luca Fülbier


2 Answers

Is the use of classes in a typical OO fashion discouraged in F#?

It's not discouraged, but it's not the first place most experienced F# developers would go. Most F# developers will avoid subclassing and OO paradigms, and instead go with records or discriminated unions, and functions to operate on them.

Are immutable classes preferred?

Immutability should be preferred when possible. That being said, immutable classes can often be represented other ways (see below).

What is the preferred way to access/alter class data in F#? (Class member functions and get/set or free functions which allow piping? What about static members to allow piping and providing the functions with a fitting namespace?)

This is typically done via functions that allow piping, though access can be done directly, as well.


For your above code, it would be more common to use a record instead of a class, and then put the functions which work on the record into a module. An "immutable class" like yours can be written as a record more succinctly:

type BankAccount = { balance : float ; status : AccountStatus }

Once you've done this, working with it becomes easier, as you can use with to return modified versions:

let openAccount (acc: BankAccount) =
    { acc with status = Open }

Note that it'd be common to put these functions into a module:

module Account =
    let open acc =
       { acc with status = Open }
    let close acc =
       { acc with status = Closed }
like image 97
Reed Copsey Avatar answered Oct 21 '22 10:10

Reed Copsey


Question: Is the use of classes in a typical OO fashion discouraged in F#?

It is not against F#'s nature. I think that there are cases when this is justified.

However, usage of classes should be limited if developers want to take full advantage of F# strengths (e.g. type interference, ability to use functional patterns such as partial application, brevity) and are not constrained by legacy systems and libraries.

F# for fun and profit gives a quick summary of pros and cons of using classes.

Ouestion: Are immutable classes preferred? ( I found them to be confusing in the above example)

Sometimes yes, sometimes not. I think that immutability of classes gives you lots of advantages (it's easier to reason about type's invariants etc.) but sometimes immutable class can be a bit cumbersome to use.

I think that this question is a bit too broad - it's somewhat similar to a question if fluent interfaces are preferred in object-oriented design - the short answer is: it depends.

What is the preferred way to access/alter class data in F#? (Class member functions and get/set or free functions which allow piping? What about static members to allow piping and providing the functions with a fitting namespace?)

Piping is a canonical construct in F#, so I would go for static member. If your library is consumed in some other languages, you should include getter and setter inside class as well.

EDIT:

FSharp.org has a list of quite specific design guidelines which include:

✔ Do use classes to encapsulate mutable state, according to standard OO methodology.

✔ Do use discriminated unions as an alternative to class hierarchies for creating tree-structured data.

like image 41
Tomasz Maczyński Avatar answered Oct 21 '22 10:10

Tomasz Maczyński