F# – Record types vs classes

Yan Cui

I help clients go faster for less using serverless technologies.

This article is brought to you by

Don’t reinvent the patterns. Catalyst gives you consistent APIs for messaging, data, and workflow with key microservice patterns like circuit-breakers and retries for free.

Try the Catalyst beta

In F#, you can defined record types which differ from tuples and discriminated unions in that they allow you to organize values into a type and name those values through fields:

image

Looks like a cut-down version of a standard .Net class with a couple of properties? Yes they do, but records offer several advantages over the .Net classes.

Type Inference

One of the biggest advantage records offer is that it works seamlessly with F#’s type inference system. Whereas classes must be annotated in order to be used, record types can be inferred by the fields you access!

Consider this simple function:

image

and see what its function signature reads:

image

Magic! Well, with a caveat :-P As you can imagine, if you define a second record type which contains the same set of fields then it might just confuses the compiler a little. For instance, if you define a record type for employees:

image

The function signature for the printPerson function will become:

image

So to tell the printPerson function to use the Person record instead, you’ll need to annotate the parameter p:

image

Immutability

Unlike classes, record fields are immutable by default. This immutability by default approach (zero side effect) is inline with everything else in F# and in functional languages in general, and offers a degree of safety guarantee.

However, whether immutability is an advantage very much depends on your use case, Eric Lippert made an excellent argument here that the lack of side effects and immutable data structures function languages offer are both advantages and disadvantages.

Fortunately, with F# record types, it’s possible to change the field values, either by using the copy and update record expression:

image

or you can explicitly specify that a field should be mutable:

image

then you are allowed to update the value of the mutable fields:

image

No Inheritance

Records cannot be inherited, which gives you a future guarantee of safety but then again you can equally make arguments against it, another post another day maybe..

Pattern Matching

Records can be used as part of standard pattern matching whereas classes cannot without resorting to active patterns or with when guards.

This is important, as you’re likely to be using pattern matching everywhere in your code and frankly, why wouldn’t you? It’s so awesome ;-) You can mach the structure of records and not every record field needs to be a part of the pattern match:

image

Equally you can capture values and match literal:

image

Structural Equality Semantics

Standard .Net classes have reference equality semantics (two reference type variables are deemed equal if they point to the same location in memory), but records have structural equality semantics (two values are deemed equal if their fields are equal):

image

In this respect, records are the same as .Net structs, which also structural equality semantics and cannot be inherited.

Update 27/09/2011:

One thing I forgot to mention is that whilst you can define a record type with just the list of fields, you can also add methods and properties using the member keyword:

image

Whenever you’re ready, here are 3 ways I can help you:

  1. Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
  2. I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
  3. Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.

10 thoughts on “F# – Record types vs classes”

  1. Pingback: F# Performance Test – Structs vs Records | theburningmonk.com

  2. Nice code. What if I have a record where I don’t need to set all of the fields

    For example: let Person = { FirstName : string; LastName : string; Age : int; Died : DateTime }

    Then I would like to create a person record leaving out the Died field. I want to set that later using the “with” technique. Compiler will not let me do this unfortunately…

  3. Pingback: Contrasting F# and Elm’s record types | theburningmonk.com

  4. Pingback: Elm – functional reactive dreams + missile command | theburningmonk.com

  5. Hi Yan,

    Really nice post, very informative.

    I have a question related to the ability to add properties and methods to record types: do you know more about what was the reason behind introducing this feature to the language?
    To me this feels like a feature which doesn’t really fit into the functional world, where—as far as I understand—we don’t want to mix data and behavior together (contrary to what we tend to do in OOP).
    So why was this feature needed in F#?

    Thanks,
    Mark

  6. Hi Mark,

    I don’t know Don’s exact reason for including it, but my view is that:
    a) F# is a functional-first, multi-paradigm language, whilst it gently nudges you towards FP as default but leaves the door open to drop down to imperative where you need to;
    b) Practicality was an important design goal for F#, and as such it needs to strike a balance between safety and convenience, interoperability is important for the sake of convenience as it allows you to gradually introduce F# to existing .Net codebase, leverage the rich ecosystem of libraries and frameworks in .Net and so on;
    c) FP and OOP are orthogonal, when you’re programming in C#/Java you’re not only programming in OOP but also in imperative programming too. If you substitute imperative programming with FP then it’s actually not a bad place (intersection between FP and OOP) to be, and I have heard a lot of people talk about “Functional on the inside, OO on the outside”. FP and OOP both have their strengths and weaknesses, and neither is good for every situation, so I take no issues with mixing and matching them depending on the situation.

    Sorry it doesn’t answer your question exactly, but hope you find my views useful at least.

Leave a Comment

Your email address will not be published. Required fields are marked *