Fundamentals of Records

Introduction

A record is a group of variables that are considered as one entity. That is, inside the record, you can create (declare) one or more variables and the variables can be of different types. Then, when you need the services of the record, you can either access the record as a whole or access its members individually.

Creating a Record

The basic formula to create a record is:

type record-name = { labels }

You start with the type keyword, followed by a name. The name of the record follows the rules of names of types. After the name, type = followed by an opening and a closing curly brackets. This would be done as follows:

type Road = { ... }

A Label in a Record

Inside the brackets, create one or more members. A member in a record is also called a label. The labels are separated by semicolons. Each label must use the following formula:

label-name : data-type

That is, each label must be created with a name and its data type, both separated by a colon.

A record can have just one label. Here is an example:

type Student = { FullName : string }

Otherwise, a record can have as many labels as you want. You can create the members on the same line. In that case, separate the members with semicolons. Here is an example:

type Student = { ID : int; FirstName : string; LastName : string; Gender : char }

If there are many labels or to make the code easier to read, you can list the labels on different lines, or each on its own line. Here is an example:

type Student = {
    ID : int;
    FirstName : string;
    LastName : string;
    Gender : char
    }

If you use this technique, you can omit the semicolons. Here is an example:

type Student = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
    }

Just like you can create one record in a file, you can created as many records as you want in the same file.

Initializing a Record

Before using a record, you should assign a value to each of its labels. You do this by creating a record expression. To create a record expression, use the following formula:

let variable-name = { label-name1 = value1; label-name2 = value2; . . .; label-name_n = value_n }

As always, start with let followed by a name for the variable and assign curly brackets to it. Inside the brackets, enter each label of the record and assign a value to it; separate them with semicolons. Here is an example:

type Student = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
    }

let std = { ID = 773946; FirstName = "Amir"
            LastName = "Shalloub" Gender = 'M' }

Remember that you can write each label on its own line to make the code easier to read, in which case you can omit the semicolons:

type Student = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
    }

let std = {
    ID = 773946
    FirstName = "Amir"
    LastName = "Shalloub"
    Gender = 'M'
    }

Notice that the name of the record is not specified in the declaration, but the compiler must be able to identify or recognize the labels that are being accessed in the curly brackets of the variable. This also means that the record must be created before the variable that initializes it.

Introduction to Using a Record

Accessing a Record

After initializing a record, one way you can use it consists of presenting its values to the user. To do this, use the printfn() function and specify %A as the value placeholder. Here is an example:

type Country = {
    Continent: string
    Name : string
    Capital : string
    Area : uint32
    InternetCode: string
    }

let Ecuador = {
    Continent = "South America"
    Name = "Ecuador"
    Capital = "Quito"
    Area = 283561u
    InternetCode = "ec"
    }

printfn "%A" Ecuador

This would produce:

{Continent = "South America"
 Name = "Ecuador"
 Capital = "Quito"
 Area = 283561u;
 InternetCode = "ec"}
Press any key to close this window . . .

Accessing the Labels of a Record

To access a label of a record, type the name of the variable you declared, followed by a period, followed by the label you want. Here are examples:

type Student = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
    }

let std = {
    ID = 773946
    FirstName = "Amir"
    LastName = "Shalloub"
    Gender = 'M'
    }

printfn "Student Information"
printfn "Student ID: %d" std.ID
printfn "First Name: %s" std.FirstName
printfn "Last Name:  %s" std.LastName
printfn "Gender:     %c" std.Gender

This would produce:

Student Information
Student ID: 773946
First Name: Amir
Last Name:  Shalloub
Gender:     M
Press any key to close this window . . .

The Type of an Object

When creating an object from a record, the compiler is able to identify the record for which you are creating the object. In some cases, and sometimes this is helpful or necessary, you can or should specify the type of the object. To this, when declaring the variable, after the name of the object, type a colon followed by the name of the record. Here is an example:

type Tractor = {
    ModelName    : string
    EnginePower  : float
    LiftCapacity : int
    MowerHeight  : int
    Price        : float
}

let machine : Tractor = {
    ModelName    = "MW9724"
    EnginePower  = 21.5
    LiftCapacity = 754
    MowerHeight  = 60
    Price        = 13_050
}

stdout.WriteLine("Tractor")
printfn "%A" machine
stdout.WriteLine("================================")

This would produce:

Tractor
{ ModelName = "MW9724"
  EnginePower = 21.5
  LiftCapacity = 754
  MowerHeight = 60
  Price = 13050.0 }
================================

Press any key to close this window . . .

Using many Records

In an application, you can create and/or use many records. Here is an example:

type Tractor = {
    ModelName    : string
    EnginePower  : float
    LiftCapacity : int
    MowerHeight  : int
    Price        : float
}

type VehicleRegistration = {
    TagNumber : string
    Make      : string
    Model     : string
    Year      : int
}

let vr : VehicleRegistration = {
    TagNumber = "HPR-385"
    Make = "Ford"
    Model = "Escape"
    Year = 2020
}

let machine : Tractor = {
    ModelName    = "MW9724"
    EnginePower  = 21.5
    LiftCapacity = 754
    MowerHeight  = 60
    Price        = 13_050
}

stdout.WriteLine("Vehicle Registration")
printfn "%A" vr
stdout.WriteLine("--------------------------------")
stdout.WriteLine("Tractor")
printfn "%A" machine
stdout.WriteLine("================================")

This would produce:

Vehicle Registration
{ TagNumber = "HPR-385"
  Make = "Ford"
  Model = "Escape"
  Year = 2020 }
--------------------------------
Tractor
{ ModelName = "MW9724"
  EnginePower = 21.5
  LiftCapacity = 754
  MowerHeight = 60
  Price = 13050.0 }
================================

Press any key to close this window . . .

Sometimes, two or more records may have the labels with the same names. Here are examples:

type Student = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
    }

type Employee = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
    }

Notice that both the Student and the Employee records have the exact same labels. This situation qualifies as ambiguous (common in functional and logical languages). In this case, when you declare a variable that initializes a record, the variable will use the record that is closest to it, which is the most recently created record. In our case, this would be the Employee record. Here is an example:

type Student = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
    }

type Employee = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
    }

let std = {
    ID = 773946
    FirstName = "Amir"
    LastName = "Shalloub"
    Gender = 'M'
    }

If you have different records that have labels that use the same name, when declaring a variable that initializes them, you should qualify one or each label in the curly brackets of the variable. To do this, type the name of the record, followed by a period, and followed by the name of the label. Then assign the appropriate value to the label. Here are examples:

type Student = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
}

type Employee = {
    ID : int
    FirstName : string
    LastName : string
    Gender : char
}

let std = {
    Student.ID = 773946
    Student.FirstName = "Amir"
    Student.LastName = "Shalloub"
    Student.Gender = 'M'
}

let contractor = {
    Employee.ID = 50225
    Employee.FirstName = "Hellie"
    Employee.LastName = "Pastore"
    Employee.Gender = 'F'
}

In the same way, when accessing a member of an object, you should qualify the member with the name of its variable. Here are examples:

type Student = {
    ID        : int
    FirstName : string
    LastName  : string
    Gender    : char
}

type Employee = {
    ID        : int
    FirstName : string
    LastName  : string
    Gender    : char
}

let std = {
    Student.ID        = 773946
    Student.FirstName = "Amir"
    Student.LastName  = "Shalloub"
    Student.Gender    = 'M'
}

let contractor = {
    Employee.ID        = 50225
    Employee.FirstName = "Hellie"
    Employee.LastName  = "Pastore"
    Employee.Gender    = 'F'
}

printfn "Student Information"
printfn "Student ID: %d" std.ID
printfn "First Name: %s" std.FirstName
printfn "Last Name:  %s" std.LastName
printfn "Gender:     %c" std.Gender

printfn "------------------------"
printfn "Employee Record"
printfn "Employee ID: %d" contractor.ID
printfn "First Name:  %s" contractor.FirstName
printfn "Last Name:   %s" contractor.LastName
printfn "Gender:      %c" contractor.Gender
printfn "========================"

This would produce:

Student Information
Student ID: 773946
First Name: Amir
Last Name:  Shalloub
Gender:     M
------------------------
Employee Record
Employee ID: 50225
First Name:  Hellie
Last Name:   Pastore
Gender:      F
========================

Press any key to close this window . . .

Copying a Record

After creating an object from a record, you may want to create another object whose some labels use the same values as some labels of the previous object(s). In this case, you can copy the similar values. To do this, in the {} of the new object, type the name of the object followed by with, the name of the label whose value you want to change and assign the desired value to it. Here is an example:

type Apartment = {
    UnitNumber      : string
    Bedrooms        : int
    Bathrooms       : float
    SecurityDeposit : int
    MonthlyRate     : int }
 
let a508293 = { UnitNumber = "102"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950 }
let a729397 = { a508293 with UnitNumber = "103" }

printfn "Apartment: %A" a508293
printfn "Apartment: %A" a729397

This would produce:

Apartment: {UnitNumber = "102"
 Bedrooms = 1;
 Bathrooms = 1.0;
 SecurityDeposit = 500;
 MonthlyRate = 950;}
Apartment: {UnitNumber = "103"
 Bedrooms = 1;
 Bathrooms = 1.0;
 SecurityDeposit = 500;
 MonthlyRate = 950;}
Press any key to close this window . . .

Notice that the second object gets all its values from the object copied, except for the label whose value was changed. In the same way, you can change the value of any of the labels that are different from the copied object. Here are examples:

type Apartment = {
    UnitNumber      : string
    Bedrooms        : int
    Bathrooms       : float
    SecurityDeposit : int
    MonthlyRate     : int }
 
let a399475 = { UnitNumber = "101"; Bedrooms = 2; Bathrooms = 2.00; SecurityDeposit = 650; MonthlyRate = 1150 }
let a508293 = { UnitNumber = "102"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950 }
let a729397 = { a508293 with UnitNumber = "103" }
let a809387 = { UnitNumber = "104"; Bedrooms = 3; Bathrooms = 2.00; SecurityDeposit = 850; MonthlyRate = 1350 }
let a486360 = { a508293 with UnitNumber = "105"; MonthlyRate =  1050 }
let a273004 = { a399475 with UnitNumber = "106"; Bathrooms = 1.00; MonthlyRate = 1050 }

printfn "Apartment: %A" a399475
printfn "Apartment: %A" a508293
printfn "Apartment: %A" a729397
printfn "Apartment: %A" a809387
printfn "Apartment: %A" a486360
printfn "Apartment: %A" a273004

This would produce:

Apartment: {UnitNumber = "101"
 Bedrooms = 2
 Bathrooms = 2.0
 SecurityDeposit = 650
 MonthlyRate = 1150}
Apartment: {UnitNumber = "102"
 Bedrooms = 1
 Bathrooms = 1.0
 SecurityDeposit = 500
 MonthlyRate = 950}
Apartment: {UnitNumber = "103"
 Bedrooms = 1
 Bathrooms = 1.0
 SecurityDeposit = 500
 MonthlyRate = 950}
Apartment: {UnitNumber = "104"
 Bedrooms = 3
 Bathrooms = 2.0
 SecurityDeposit = 850
 MonthlyRate = 1350}
Apartment: {UnitNumber = "105"
 Bedrooms = 1
 Bathrooms = 1.0
 SecurityDeposit = 500
 MonthlyRate = 1050}
Apartment: {UnitNumber = "106"
 Bedrooms = 2
 Bathrooms = 1.0
 SecurityDeposit = 650
 MonthlyRate = 1050}
Press any key to close this window . . .

A Mutable Field

By default, after creating an object, you cannot change the values of its labels. If you want to create a label whose value can change, you must create it as mutable. Here is an example:

type Apartment = {
    UnitNumber      : string
    Bedrooms        : int
    Bathrooms       : float
    SecurityDeposit : int
    mutable MonthlyRate     : int }

Before using it, first create an object based on the record. To change the value of the field, type the name of the object, a period, and the name of the field. Then assign the desired value to it, using the <- operator. Here are examples:

type Apartment = {
    UnitNumber      : string
    Bedrooms        : int
    Bathrooms       : float
    SecurityDeposit : int
    mutable MonthlyRate     : int }
 
let a399475 = { UnitNumber = "101"; Bedrooms = 2; Bathrooms = 2.00; SecurityDeposit = 650; MonthlyRate = 1150 }
let a508293 = { UnitNumber = "102"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950 }

printfn "Apartment: %A" a399475
printfn "Apartment: %A" a508293

a399475.MonthlyRate <- 1075
a508293.MonthlyRate <- 1025

printfn "\nThe monthly rent has changed\n"

printfn "The new tenant will get a discount"
printfn "Apartment: %A" a399475
printfn "The new tenant will see an increase in rent"
printfn "Apartment: %A" a508293

This would produce:

Apartment: {UnitNumber = "101"
 Bedrooms = 2
 Bathrooms = 2.0
 SecurityDeposit = 650
 MonthlyRate = 1150}
Apartment: {UnitNumber = "102"
 Bedrooms = 1
 Bathrooms = 1.0
 SecurityDeposit = 500
 MonthlyRate = 950}
 
The monthly rent has changed

The new tenant will get a discount
Apartment: {UnitNumber = "101"
 Bedrooms = 2
 Bathrooms = 2.0
 SecurityDeposit = 650
 MonthlyRate = 1075}
The new tenant will see an increase in rent
Apartment: {UnitNumber = "102"
 Bedrooms = 1;
 Bathrooms = 1.0;
 SecurityDeposit = 500;
 MonthlyRate = 1025;}
Press any key to close this window . . .

Records and Functions

Passing a Record

A record can be passed as argument to a function. To do this, include the argument in parentheses and specify the name of the record as data type. Here is an example:

let describe(road : RoadSystem) = . . .

In the body of the function, you can access each member of the record by applying the period operator on the argument followed by the desired member of the record. Here is an example:

type RoadSystem = {
    RoadName : string
    Distance : float
    Location  : string }
    
let describe (road : RoadSystem) =
    printfn "Road System Database"
    printfn "=----------------------------------------------------="
    printfn "Road Name: %s" road.RoadName
    printfn "Distance:  %0.2f miles" road.Distance
    printfn "Location:  %s\n" road.Location

let USRoute1 =
    {
        RoadName = "U.S. Route 1"; Distance = 2369.00;
        Location = "From FortKent, Maine To Key West, Florida"
    }    

describe USRoute1

This would produce:

Road System Database
=----------------------------------------------------=
Road Name: U.S. Route 1
Distance:  2369.00 miles
Location:  From FortKent, Maine To Key West, Florida

Press any key to close this window . . .

Inside the function, besides accessing it, you can use or manipulate the record in any appropriate and allowed way. Here is an example that checks the values of some of the members of a record:

type RoadSystem = {
    RoadName : string
    Distance : float
    Location  : string }
    
let describe (road : RoadSystem) =
    printfn "Road System Database"
    printfn "=----------------------------------------------------="
    printfn "Road Name: %s" road.RoadName
    if road.Distance > 0.00 then
        printfn "Distance:  %0.2f miles" road.Distance
    else
        printfn "Distance:  Unknown"
    match road.Location with
    | "" -> printfn "Location:  Unknown\n"
    | _ -> printfn "Location:  %s\n" road.Location

let wa =
    {
        RoadName = "Wisconsin Avenue"; Distance = -1.00
        Location = ""
    }    

describe wa

This would produce:

Road System Database
=----------------------------------------------------=
Road Name: Wisconsin Avenue
Distance:  Unknown
Location:  Unknown

Press any key to close this window . . .

Returning a Record

A function can be made to return a record. At a minimum, you can have a function that creates its own record object and returns it.Here is an example:

let create() = 
    let road = {
        RoadName = "U.S. Route 1"
        Distance = 2369.00
        Location = "From FortKent, Maine To Key West, Florida" }
    road // This is the returned record object

You can then call the function to retrieve the object it returns and use that object as you see fit. Here is an example:

type RoadSystem = {
    RoadName : string
    Distance : float
    Location  : string }

let create() = 
    let road = {
        RoadName = "U.S. Route 1"
        Distance = 2369.00
        Location = "From FortKent, Maine To Key West, Florida" }
    road // This is the returned record object
    
let describe (road : RoadSystem) =
    printfn "Road System Database"
    printfn "=----------------------------------------------------="
    printfn "Road Name: %s" road.RoadName
    printfn "Distance:  %0.2f miles" road.Distance
    printfn "Location:  %s\n" road.Location
    
let one = create() 
describe one

On the other hand, a function can be supplied some values as arguments and use all or some of those values to initialize a record and return it. Here is an example:

let create name dist loc = 
    let road = {
        RoadName = name
        Distance = dist
        Location = loc }
    road // This is the returned record object

When calling the function, make sure you provide the appropriate values in the order the function expects them. You can get the value produced by that function and use its record object. Here is an example:

type RoadSystem = {
    RoadName : string
    Distance : float
    Location  : string }

let create name dist loc = 
    let road = {
        RoadName = name
        Distance = dist
        Location = loc }
    road // This is the returned record object
    
let describe (road : RoadSystem) =
    printfn "Road System Database"
    printfn "=----------------------------------------------------="
    printfn "Road Name: %s" road.RoadName
    printfn "Distance:  %0.2f miles" road.Distance
    printfn "Location:  %s\n" road.Location
    
let EastWestHighway = create "MD 410" 13.92 "From MD 355 in Bethesda to Landover Hills"
describe EastWestHighway

This would produce:

Road System Database
=----------------------------------------------------=
Road Name: MD 410
Distance:  13.92 miles
Location:  From MD 355 in Bethesda to Landover Hills

Press any key to close this window . . .

In such a case, since the function would create and return a record object, you can make it responsible to verify and validate the values of the members in order to produce an appropriate object.

Pattern Matching

Various combinations of objects of a record can be used in pattern matching to find out what combination is valid for a particular scenario. The pattern to compare should be a sample object. The options can be various objects to which to compare the pattern. You can then take action based on the option that matches the pattern. Here is an example:

(* This is a record type representing employees who work in a commercial bank.
   In this simple example of a record, each employee record determines
   where the employee is currently working (today) in the office or from home.*)
type Employee = {
    EmployeeNumber : string
    WorkFrom       : string }

(* These records indicate where the employee is working today
   Notice that the same employee number can have more than one record but
   working either in the office or from home (on different days. *)
let e279374o = { EmployeeNumber = "279-374"; WorkFrom = "Office" }
let e635753o = { EmployeeNumber = "635-753"; WorkFrom = "Office" }
let e279374h = { EmployeeNumber = "279-374"; WorkFrom = "Home"   }
let e826846o = { EmployeeNumber = "826-846"; WorkFrom = "Office" }

let evaluate (empl : Employee) =
    let mutable employeeCanProcessAccount = false
    (* This pattern matching is meant to find out where the employee is 
       working today. Employees are not allowed to modify customers' 
       accounts when they work from home. So even if an employee is a 
       manager but working from home, he or she is not allowed to open 
       new bank accounts or to make changes on existing bank accounts*)
    match empl with
    | { EmployeeNumber = "279-374"; WorkFrom = "Office" } -> employeeCanProcessAccount <- true
    | { EmployeeNumber = "635-753"; WorkFrom = "Office" } -> employeeCanProcessAccount <- true
    | { EmployeeNumber = "279-374"; WorkFrom = "Home"   } -> employeeCanProcessAccount <- false
    | { EmployeeNumber = "826-846"; WorkFrom = "Office" } -> employeeCanProcessAccount <- true
    | _ -> employeeCanProcessAccount <- true
    employeeCanProcessAccount

let mutable result = evaluate e635753o
printfn "Today, employee # e635753o can process customer's accounts: %A" result
printfn "______________________________________________________________________"
result <- evaluate e279374h
printfn "Today, employee # e635753o can process customer's accounts: %A" result

This would produce:

Today, employee # e635753o can process customer's accounts: true
______________________________________________________________________
Today, employee # e635753o can process customer's accounts: false
Press any key to close this window . . .

Records and Other Types

Records and Enumerations

Just like the primitive types, a member of a record can be created as an enumeration type. Of course, you must use an existing enumeration. You can use one of the existing types such as those defined in the .NET Framework, or you can create your own enumeration. Here is an example:

type RoadType =
    | . . .
    
type RoadSystem = {
    RoadName : string
    Category : RoadType
    Distance : float
    Location  : string }

When creating an object from the record, make sure you assign a member of the enumeration to the member of the record. You can then access the enumerated member outside the record. Here is an example:

type RoadType =
    | Court
    | Street
    | Road
    | Avenue
    | Boulevard
    | StateRoad
    | StateHighway
    | Interstate
    | Unknown
    
type RoadSystem = {
    RoadName : string
    Category : RoadType
    Distance : float
    Location  : string }

let USRoute1 =
    {
        RoadName = "U.S. Route 1"
        Category = RoadType.Road
        Distance = 2369.00
        Location = "From FortKent, Maine To Key West, Florida"
    }    
    
printfn "Road System Database"
printfn "=----------------------------------------------------="
printfn "Road Name: %s" USRoute1.RoadName
printfn "Category:  %A" USRoute1.Category
printfn "Distance:  %0.2f miles" USRoute1.Distance
printfn "Location:  %s\n" USRoute1.Location

This would produce:

Road System Database
=----------------------------------------------------=
Road Name: U.S. Route 1
Category:  Road
Distance:  2369.00 miles
Location:  From FortKent, Maine To Key West, Florida

Press any key to close this window . . .

In the same way, you can manipulate the enumerated member in any appropriate way.

A Record as a Record Label

A label of a record can be a record type. When creating the label, make sure you appropriately specify its type. Here is an example:

type Department = {
    DepartmentCode : string
    DepartmentName : string }

type Employee = {
    EmployeeNumber : string
    FirstName      : string
    LastName       : string
    Department     : Department
    Title          : string }

Before creating values for the new record, you can first create values for the first record and use its variable as the value in the new object. Here is an example:

type Department = {
    DepartmentCode : string
    DepartmentName : string }

type Employee = {
    EmployeeNumber : string
    FirstName      : string
    LastName       : string
    Department     : Department
    Title          : string }

let dept = {
    DepartmentCode = "ADMN";
    DepartmentName = "Administration, Admissions, and Students Affairs" }
let staff = {
    EmployeeNumber = "161138"
    FirstName = "Laura"
    LastName = "Fannie"
    Department = dept
    Title = "Dean of Litterary Studies" }

printfn "University Information"
printfn "------------------------------------------------------"
printfn "Employee Record"
printfn "EmployeeN #:        %s" staff.EmployeeNumber
printfn "First Name:         %s" staff.FirstName
printfn "LastName:           %s" staff.LastName
printfn "Department Record ____________________________________"
printfn "   Department Code: %s" staff.Department.DepartmentCode
printfn "   Department Name: %s" staff.Department.DepartmentName
printfn "Employee Title:     %s" staff.Title
printfn "======================================================"

This would produce:

University Information
------------------------------------------------------
Employee Record
EmployeeN #:        161138
First Name:         Laura
LastName:           Fannie
Department Record ____________________________________
   Department Code: ADMN
   Department Name: Administration, Admissions, and Students Affairs
Employee Title:     Dean of Litterary Studies
======================================================
Press any key to close this window . . .

As an alternative, you can specify the values of the member when creating the object. Here is an example:

type Department = {
    DepartmentCode : string
    DepartmentName : string }

type Employee = {
    EmployeeNumber : string
    FirstName      : string
    LastName       : string
    Department     : Department
    Title          : string }

let dept = { DepartmentCode = "ADMN"; DepartmentName = "Administration, Admissions, and Students Affairs" }
let staff = {
    EmployeeNumber = "161138";
    FirstName = "Laura"; LastName = "Fannie";
    Department = { DepartmentCode = "ADMN"; DepartmentName = "Administration, Admissions, and Students Affairs" };
    Title = "Dean of Litterary Studies" }

Records and Classes

A member of a class can be created from a record. Just as you can pass a record as argument to a function, you can pass a record to a constructor of a class. In the same way, a property of a class can be created from a record. Here are examples:

type Student = {
    StudentNumber : string
    FirstName : string
    LastName : string }

type Course = {
    CourseCode : string
    CourseName : string
    Credits : int }

type Enrollment(number, date, student, course, semester) =
    let mutable nbr = number
    let mutable dte = date
    let mutable std = student
    let mutable crs = course
    let mutable sms = semester
    member this.EnrollmentNumber with get() = nbr and set(value) = nbr <- value
    member this.DateEnrolled with get() = dte and set(value) = dte <- value
    member this.Student with get() = std and set(value) = std <- value
    member this.Course with get() = crs and set(value) = crs <- value
    member this.Semester with get() = sms and set(value) = sms <- value

let student = { StudentNumber = "502-448"; FirstName = "Jonathan"; LastName = "Davidson" }
let course = { CourseCode = "CMSC 108"; CourseName = "Introduction to F# Programming"; Credits = 3 }
let enroll = Enrollment(100001, "12/12/2014", student, course, "FALL 2015")

printfn "School Registration Summary"
printfn "---------------------------"
printfn "Enrollment #:        %d" enroll.EnrollmentNumber
printfn "Enrollment Date:     %s" enroll.DateEnrolled
printfn "Student Information: %A" enroll.Student
printfn "Course Details:      %A" enroll.Course
printfn "Semester Enrolled:   %s" enroll.Semester

This would produce:

School Registration Summary
---------------------------
Enrollment #:        100001
Enrollment Date:     12/12/2014
Student Information: {StudentNumber = "502-448";
 FirstName = "Jonathan";
 LastName = "Davidson";}
Course Details:      {CourseCode = "CMSC 108";
 CourseName = "Introduction to F# Programming";
 Credits = 3;}
Semester Enrolled:   FALL 2015

Press any key to close this window . . .

Once you have used a record as a member/property of a class, you can access the individual members of that record and use them as you see fit, such as to display their values. Here are examples:

type Student = {
    StudentNumber : string
    FirstName : string
    LastName : string }

type Course = {
    CourseCode : string
    CourseName : string
    Credits : int }

type Enrollment(number, date, student, course, semester) =
    let mutable nbr = number
    let mutable dte = date
    let mutable std = student
    let mutable crs = course
    let mutable sms = semester
    member this.EnrollmentNumber with get() = nbr and set(value) = nbr <- value
    member this.DateEnrolled with get() = dte and set(value) = dte <- value
    member this.Student with get() = std and set(value) = std <- value
    member this.Course with get() = crs and set(value) = crs <- value
    member this.Semester with get() = sms and set(value) = sms <- value

let student = { StudentNumber = "502-448"; FirstName = "Jonathan"; LastName = "Davidson" }
let course = { CourseCode = "CMSC 108"; CourseName = "Introduction to F# Programming"; Credits = 3 }
let enroll = Enrollment(100001, "12/12/2014", student, course, "FALL 2015")

printfn "School Registration Summary"
printfn "------------------------------------------------------"
printfn "Enrollment #:       %d" enroll.EnrollmentNumber
printfn "Enrollment Date:    %s" enroll.DateEnrolled
printfn "Student Information-----------------------------------"
printfn "   Student #:       %s" enroll.Student.StudentNumber
printfn "   First Name:      %s" enroll.Student.FirstName
printfn "   LastName:        %s" enroll.Student.LastName
printfn "Course Details----------------------------------------"
printfn "   Course Code:     %s" enroll.Course.CourseCode
printfn "   Course Name:     %s" enroll.Course.CourseName
printfn "   Course Credits:  %d" enroll.Course.Credits
printfn "Semester Enrolled:  %s" enroll.Semester
printfn "======================================================"

This would produce:

School Registration Summary
------------------------------------------------------
Enrollment #:       100001
Enrollment Date:    12/12/2014
Student Information-----------------------------------
   Student #:       502-448
   First Name:      Jonathan
   LastName:        Davidson
Course Details----------------------------------------
   Course Code:     CMSC 108
   Course Name:     Introduction to F# Programming
   Course Credits:  3
Semester Enrolled:  FALL 2015
======================================================
Press any key to close this window . . .

Generic Records

We know that a record is structural type that contains named members where each member has a type. Here is an example:

type Country = {
    Continent: string
    Name : string
    Capital : string
    Area : uint32
    InternetCode: string
    }

A generic record is one where the data type of one or more members is not specified. To create a generic record, after the name of the record, type <>. Inside the operator, if all record members use the same type, enter a letter or word precedede by '. In the body of the record, use the apostrophe and the generic letter or word in place of the data type. Here is an example:

type Country<'T>  = {
    Continent : 'T
    Name : 'T
    Capital : 'T
}

Remember that, to use a record, you can declare a variable and give a value to each member. Here are examples:

type Country<'T>  = {
    Continent : 'T
    Name : 'T
    Capital : 'T
}

let Ecuador = {
    Continent = "South America"
    Name = "Ecuador"
    Capital = "Quito"
}

printfn "%A" Ecuador

If the various members of the record use different types, use two letters or words inside the <> operator applied to the name of the record. On the right side of each member of the record, apply the desired generic letter or generic word. Here are examples:

type Country<'s, 'i>  = {
    Continent : 's
    Name : 's
    Capital : 's
    Area : 'i
    InternetCode : 's
}

let Ecuador = {
    Continent = "South America"
    Name = "Ecuador"
    Capital = "Quito"
    Area = 283561u
    InternetCode = "ec"
}

printfn "%A" Ecuador

Built-In Records: Reference Cells

Introduction

When we started using variables, we were introduced to reference cells and we saw how to use them to control the properties of a class. To actually support the concept of getting a reference to th cell memory where a variable is stored, the F# language provides a built-in generic record named Ref defined in the Microsoft.FSharp.Core namespace. The Ref record supports various operators and is equipped with a property and a field.

Creating a Reference Cell

We already know that, to declare a variable and get a reference cell where it would be located, we can assign an initial value preceded with the ref keyword. Here is an example:

let price = ref 45.00

To change the value of the variable, you can use the := operator. Another technique is to use the following formula:

(:=) variable-name value

Here is an example:

let price = ref 45.00

// . . .
     
(:=) price 38.75

Getting the Value of a Reference Cell

To get the value of the variable, you can apply the ! operator before the name of the variable. Another technique is to use the following formula:

(!) variable-name

Here is an example:

let price = ref 45.00;

printfn "Item Price: $%0.02f" ((!) price)

The Contents of a Reference Cell

To support the value of the variable, the Ref record is equipped with a property named Value and a field named contents. Both allow you to get the value of the variable. Here examples of using them

let unitPrice = 124.95
let markedPrice = ref 0.00
let discountRate = ref 25.00

markedPrice.Value <- unitPrice
printfn "Item Price:  $%0.02f" ((!) markedPrice)

(:=) markedPrice (unitPrice - (!discountRate * unitPrice / 100.00))

printfn "Discount Price: $%0.02f\n" markedPrice.contents

This would produce:

Item Price: $124.95
Discount Price:  $93.71

Press any key to close this window . . .

The Ref.contents field and the Ref.Value property both also allow you to change the value of the variable. This is done by using the <- operator and the value to assign. Here are examples:

let unitPrice = 124.95
let markedPrice = ref 0.00;
let discountRate = ref 25.00;

markedPrice.Value <- unitPrice
printfn "Item Price: $%0.02f" ((!) markedPrice);

(:=) markedPrice (unitPrice - (!discountRate * unitPrice / 100.00))

printfn "Discount Rate:         %0.02f%s" discountRate.Value "%"
printfn "Price after Discount: $%0.02f" markedPrice.contents;

discountRate.Value   <- 50.00
markedPrice.contents <- !markedPrice - (discountRate.Value * !markedPrice / 100.00)

printfn "Discount Rate:         %0.02f%s" discountRate.Value "%"
printfn "New Marked Value:     $%0.02f\n" markedPrice.contents;

This would produce:

Item Price: $124.95
Discount Rate:         25.00%
Price after Discount: $93.71
Discount Rate:         50.00%
New Marked Value:     $46.86

Press any key to close this window . . .

Previous Copyright © 2009-2024, FunctionX Wednesday 1 November 2023 Next