F# Records

Introduction

In this article, we’ll explore F#’s records. It is similar to a tuple where the elements are named.

However, unlike tuples, records cannot be built on the fly and they must be declared in advance. 

Before we go deeper with F#'s records I do recommend reading the previous article about F#’s tuple.

OK, let's get started then. 

Record Type

F# developers when they want to group there data into a structured format without needing heavy syntax, F# record type is the answer.

Another cool thing about records it gives you a way to organize values into a type, as well as name those values through fields.

Let's try to see how to create a record type.

Create a New Record Type

Let's construct first the Customer record type and then create a new instance of it. 

module Models
type Customer = {
    firstName: string
    lastName: string
    gender: char
    phoneNumber: string
    salary: float
}

We have created a Cutomer record type under the Model module. 

Now, let's try to create a new instance of it.

(* Let's create a new Record type*)
[<Fact>]
let ``
Create A New Record``() =
let customer = {
        firstName = "Jin Vincent"
        lastName = "Necesario"
        gender = 'M'
        phoneNumber = "639551912324"
        salary = 160000.0
    }
    ( * Full namespace: Microsoft.FSharp.Reflection.FSharpType.IsRecord * )
Assert.True(FSharpType.IsRecord(customer.GetType()))

Creating a new instance seems easy, right? 

However in order to verify that the customer instance is a record type, we have used the method Microsoft.FSharp.Reflection.FSharpType.IsRecord to verify if it is true. 

Difference Between Record and Classes 

If you're a .NET developer you may see records/record type as classes.

In other words, you might see records/record type as simplified standard .NET classes. 

Records aren't simplified .NET classes they're quite different from each other.

Let's see the differences between the two: 

  • Fields 
    • Records fields are automatically exposed as properties
    • Classes fields aren't exposed automatically due to private and protected access modifier but you can expose fields with methods and properties. 
  • Constructors (Construction of Records and Classes)
    • Records don't have constructor, in other words you can't define constructor, the creation of record type is what you have seen in the first example.
    • Classes do have constructor.

Records Advantages Over Traditional Object Oriented Structures

Here are some advantages of records

  • Cannot be inherited which gives developers a guarantee of safety. 
  • Records' fields are immutable by default, unlike class types offer no safety guarantee. 
  • It can be used as part of standard pattern matching unlike classes that cannot be without resorting to active patterns or when guards. 

More Examples
 

Get Details of a Record Type

From our first example, we have seen how we can easily create a new record type. 

This time why not get the fields a certain record type, let's see an example below. 

module Models

type Customer = 
    {
        firstName   :   string
        lastName    :   string
        gender      :   char
        phoneNumber :   string
        salary      :   float
    }
(* Let's try to get the record type details and print something out *)
[<Fact>]
let ``
Get Details of the Record Type``() =
    let customer = {
        firstName = "Jin Vincent"
        lastName = "Necesario"
        gender = 'M'
        phoneNumber = "639551912324"
        salary = 160000.0
    }
Assert.Equal("JIN VINCENT".ToUpper(), customer.firstName.ToUpper())
Assert.Equal("JIN VINCENT NECESARIO", sprintf "%s %s"(customer.firstName.ToUpper())(customer.lastName.ToUpper()))

From the example, above we have a customer record type, then we have created a new instance.

From there we wanted to make the firstName, lastName to upper case in order to compare against it literal strings in an uppercase format too.

Easy isn't it? 

Update Details of a Record Type

This time why not update a certain field of an instance of a record type.

Let's see an example below.

module Models
type Customer = {
    firstName: string
    lastName: string
    gender: char
    phoneNumber: string
    salary: float
}
 (* Let's try to update the record type and check if values have successfully been updated *)  
[<Fact>]
let ``
Update A Record``() =
let customer = {
    firstName = "Jin Vincent"
    lastName = "Necesario"
    gender = 'M'
    phoneNumber = "639551912324"
    salary = 160000.0
}
let rhian = {
    customer with firstName = "Ma. Florian";gender = 'F'
}
Assert.Equal("Ma. Florian", rhian.firstName)
Assert.Equal("MA. FLORIAN NECESARIO", sprintf "%s %s"(rhian.firstName.ToUpper())(rhian.lastName.ToUpper()))

From the example above we have updated the field firstName and gender.

This was possible because of the instance name which in this case is customer and the with keyword.  

From there we have changed the fields firstName and gender.

Update Mutable Field of a Record Type

Record type can have a mutable field, we just need to insert the keyword mutable before the fieldname. 

See the Product type below. 

module Models
type Product = {
    name: string
    description: string
    mutable quantity: int
}

Now, that we have seen a good example of a record type that has a mutable field, in our case Product has a quantity mutable field.

Let's try to see how we can update the immutable field of a record type. 

[<Fact>]
let ``
Update a Mutable Record Field``() =
let product = {
        name = "Porsche"
        description = "Sports Car"
        quantity = 10
    }
( * Let 's check if the value is still 10 before we update it into 15*)
Assert.Equal(10, product.quantity)
( * Update the value to 15 * ) product.quantity < -15( * The quantity value now should be 15 * ) Assert.True(product.quantity = 15)

From the example above we have changed the value of the mutable field named quantity into number 15. That's how simple it is. 

The symbol for mutable fields to change their values we use the left arrow symbol <-. 

Create a Record Type with Members

From the previous example, we have only created a plain record type.

You might be wondering if we can add some methods within record types. 

Let's try to put a member method within the Employee type record. 

module Models
type Employee = {
    name: string
    age: int
    employeeNumber: string
}
member this.GetFullDetails() = sprintf "Employee Number: %s | Name: %s"
this.employeeNumber this.name

Now that we have defined a record type, let's try to create a new instance of it and execute the method name GetFullDetails

[<Fact>]
let ``
Create a New Record with Members``() =
let myEmployee = {
    name = "Bruce Wayne"
    age = 50
    employeeNumber = "BATMAN01"
}
let details = myEmployee.GetFullDetails()
Assert.Equal("Employee Number: BATMAN01 | Name: Bruce Wayne", details)

If you wish to add more member methods within the Employee record type, let us know in the comment section below.

Field Label Declarations

As developers sometimes we can't ignore that our records do have same fields. 

Let's try to see an example first, then we'll discuss the importance of field label declarations. 

module Models
type Person1 = {
    name: string
    age: int
}
type Person2 = {
    name: string
    age: int
}

In our example, we have created two records Person1 and Person2 with the same fields.

We need to understand that when we have a situation like this the F# compiler will take latest reference type with the field names. 

In other words, if we have declared two record types with same fields or labels, F# will pickup the one that was last declared. 

To remove confusion, developers can prefix the fields/labels with the name of the type. 

Let's see an example below. 

[ < Fact > ]
let ``
Field Label Declarations``() =
let p1 = {
    Person1.name = "Tony Starks"
    Person1.age = 48
}
let p2 = {
    Person2.name = "Logan"
    age = 200
}
Assert.Equal("Person1", p1.GetType().Name)
Assert.Equal("Person2", p2.GetType().Name)
Assert.Equal("Tony Starks", p1.name)
Assert.Equal("Logan", p2.name)

From the example, we have used the type name then followed by the dot then the field name.

An example would be Person1.name within the p1 variable. 

Summary

In this article, we've discussed F# records type concepts and the following:

  • Create a new record type 
  • Difference between a record and a class
  • Records advantages over traditional object-oriented structures 
  • Get details of the record type 
  • Update details of a record type 
  • Update the mutable field of a record type 
  • Create a record with members
  • Field label declarations

I hope you have enjoyed this article, as I enjoyed it while writing.

You can also find the sample code here on GitHub.

Till next time, happy programming!


Similar Articles