Home

The Properties of a Class

 

Properties Fundamentals

 

Introduction

Class type definition consists of creating a simple class that doesn't need to process anything. The class would need only simple properties. To create or start the type definition of a class, use the type keyword followed by the name of the class and =. Here is an example:

type Employee =

The type definition of a class can use properties but those properties are created using the val keyword. Besides the name of the property, you must specify its data type. Here is an example:

type Employee =
    val FirstName : string;

Of course, you can have as many properties as you need. Here are examples:

type Employee =
    val EmployeeNumber : string
    val FirstName      : string
    val LastName       : string
    val HourlySalary   : float

A property created with the val keyword doesn't have to be defined. for this reason, it is referred to as self-implementing.

Initializing the Properties of a Type Definition

After creating a class, you must be able to declare a variable from it and access its properties outside the class. The only way you can declare a variable for a class is if the class has a constructor. This also means that the only way you can use a type definition is to add at least one constructor to it. As you may know already, the constructor is created using the new keywork followed by parentheses. If you are creating a primary constructor, leave the parentheses empty.

A constuctor for a type definition class has a body delimited with curly brackets. That body must be assigned to the constructor using the = operator. Here is an example:

type Employee =
    val EmployeeNumber : int
    val FirstName      : string
    val LastName       : string
    val HourlySalary   : float

    new() = {}

If you do this, every property of the class must be initialized in the body of the constructor. This means that every property must receive a default value, which depends on the type of the property. Here is an example:

type Employee =
    val EmployeeNumber : int
    val FirstName      : string
    val LastName       : string
    val HourlySalary   : float

    new() = { EmployeeNumber = 0; FirstName = ""; LastName = ""; HourlySalary = 0.00 }

After doing this, you can declare a variable for the class and use it as you see fit. Here is an example:

type Employee =
    val EmployeeNumber : int
    val FirstName      : string
    val LastName       : string
    val HourlySalary   : float

    new() = { EmployeeNumber = 0; FirstName = ""; LastName = ""; HourlySalary = 0.00 }

let staff = new Employee()

In the class, you can create as many constructors as you want as long as they have different signatures. Each subsequent constructor must take a parameter that corresponds to a val member. When initializing the val member variable, assign the parameter to it. If a constructor takes fewer parameters than the number of properties, the properties that are not represented in the constructor should receive default values. Here is an example:

type Employee =
    val EmployeeNumber : int
    val FirstName      : string
    val LastName       : string
    val HourlySalary   : float

    new() = { EmployeeNumber = 0; FirstName = "";
              LastName = ""; HourlySalary = 0.00 }
    new(emplNbr) = { EmployeeNumber = emplNbr; FirstName = "";
                     LastName = ""; HourlySalary = 0.00 }
    new(emplNbr, first, last, salary) = { EmployeeNumber = emplNbr; FirstName = first;
                                          LastName = last; HourlySalary = salary }

After doing this, you can access the properties outside the class after creating an object that uses the constructor that takes a value for each property. Here is an example:

type Employee =
    val EmployeeNumber : int
    val FirstName      : string
    val LastName       : string
    val HourlySalary   : float

    new() = { EmployeeNumber = 0; FirstName = "";
              LastName = ""; HourlySalary = 0.00 }
    new(emplNbr) = { EmployeeNumber = emplNbr; FirstName = "";
                     LastName = ""; HourlySalary = 0.00 }
    new(emplNbr, first, last, salary) = { EmployeeNumber = emplNbr; FirstName = first;
                                          LastName = last; HourlySalary = salary }

let staff = new Employee(972947, "William", "Snuffy", 24.75)

staff.LastName . . .

If you want to be able to change the value of a property from an inistance of the class, declare the property as mutable. Here are examples:

type Employee =
    val mutable EmployeeNumber : int
    val mutable FirstName      : string
    val mutable LastName       : string
    val mutable HourlySalary   : float

    new() = { EmployeeNumber = 0; FirstName = "";
              LastName = ""; HourlySalary = 0.00 }
    new(emplNbr) = { EmployeeNumber = emplNbr; FirstName = "";
                     LastName = ""; HourlySalary = 0.00 }
    new(emplNbr, first, last, salary) = { EmployeeNumber = emplNbr; FirstName = first;
                                          LastName = last; HourlySalary = salary }

let staff = new Employee()

staff.EmployeeNumber <- 204958
staff.FirstName <- "Jennifer"
staff.LastName <- "Jacobs"
staff.HourlySalary <- 18.85

Initializing With Default Values

An alternative to initialize a val member is to mark it as [<DefaultValue>]. The member must also be created as mutable and the class must have a primary constructor. Here is an example:

type Customer() =
    [<DefaultValue>]
    val mutable FullName : string;

You should also create a method that can be called to initialize the val member(s). Such a method should take a parameter for the (or each) val member. In the body of the method, assign the parameter to the (or each) corresponding val member. Outside the class, access the method and pass a value for the (or each) parameter(s). Here is an example:

type Customer() =
    [<DefaultValue>]
    val mutable FullName : string
    member this.Initializer(name : string) =
        this.FullName <- name

let client = new Customer()
client.Initializer("Robert Zachary Cox")
// Accessing the val member
client.FullName

In the same way, you can create as many val members as possible and include them in your method initializer.

Details on Using Classes

 

Introdudtion

The methods of a class use most of the same features as functions. For example, you can specify the data types of parameters of methods the same way. The same applies to parameters of the constructors. Here are examples:

Although the body of a method is delimited by indenting the lines of code after the = sign, you can also put the body between begin and end. Here is an example:

open System
open System.Windows.Forms

type Creator() =
    member this.Create(title) =
	begin
            let exercise = new Form()
            exercise.Text <- title

            Application.Run exercise
	end

let app = new Creator()

app.Create "Application Development Environment"

As done for a function, to specify the return type of a method, outside its parentheses, type  a colon followed by the data type. Here is an example:

type PortionDistribution(amount, ratio1, ratio2, ratio3) =
    let totalRatios = ratio1 + ratio2 + ratio3
    let eachPortion    = amount / totalRatios

    member this.GetPortion1() : float = eachPortion * ratio1

As seen for functions, if a method is not taking an argument, you can create it either with empty parentheses, the unit type inside the parentheses, or an underscore in place of the parentheses. Here are examples:

type PortionDistribution(amount, ratio1, ratio2, ratio3) =
    let totalRatios = ratio1 + ratio2 + ratio3
    let eachPortion    = amount / totalRatios

    member this.GetPortion1() : float = eachPortion * ratio1
    member this.GetPortion2 _ = eachPortion * ratio2
    member this.GetPortion3(unit) = eachPortion * ratio3

Like a function, a method has a signature, which is a combination of its name, the types of its parameters, and its return type, all separated by asterisks.

 

Fundamentals of Properties

 

Introduction

A let binding variable is used to manage values inside a class. Such values are private and cannnot be accessed outside the class. Here is an example of of a (private) let variable:

type Payroll() =
    let summary = string

On the other hand, member variables can be used inside the class or can communicate with the outside world. The problem is that member variables are not equipped to validate or reject the values that are given to them or that they present to the clients of the class. On modern object-oriented programming, the solution is to use a special class member called a property.

A property is a member of a class that plays an intermediary role between a class and the outside objects that need to access its value(s).

Creating a Property

A property is primarily created as a member variable. It starts with the member keyword and a name. With regards to their role, there are three categories of properties.

Read-Only Properties

 

Getting the Value of the Property

A property is referred to as read-only if its role is only to make available a value from the class. To create a read-only property, you use a function named get. The formula to create a read-only property is:

member [ this | self-identifier ].property-name with get() : data-type = some-value

You start with the member keyword. This is followed by either the this keyword or a self-identifier of your choice, which can be a letter or a word. You can even use the name of the class. This is followed by a period and the desired name of the property. The name should start in uppercase and contain letters, digits, and/or underscores. The name is followed by the with get() expression.

The expression is followed by = and a value. You can get the value from a constructor of the class and assign it to the property. Here is an example of a read-only property:

type Payroll(emplNbr) =
    member me.EmployeeNumber with get() = emplNbr

In the same way, you can add as many properties as necessary. To access the value of the property outisde, create an object of the class, apply the period to the object followed by the name of the property. Here are examples:

open System
open System.Windows.Forms

type Triangle(b, h) =
    // A read-only property
    member me.Area with get() = b * h / 2.00

// Form: Geometric Triangle
let geometricTriangle = new Form()
geometricTriangle.Width  <- 265
geometricTriangle.Height <- 138
geometricTriangle.Text <- "Triangle"

let calculateArea (b : float) (h : float) = b * h / 2.00

// Label: Base
let lblBase = new Label()
lblBase.Left   <- 22
lblBase.Top    <- 19
lblBase.Width  <- 40
lblBase.Text   <- "Base:"
geometricTriangle.Controls.Add lblBase

// Text Box: Base
let txtBase = new TextBox()
txtBase.Left  <- 72
txtBase.Top   <- 16
txtBase.Width <- 54
geometricTriangle.Controls.Add txtBase

// Label: Height
let lblHeight = new Label()
lblHeight.Left <- 22
lblHeight.Top <- 50
lblHeight.Width <- 45
lblHeight.Text <- "Height:"
geometricTriangle.Controls.Add lblHeight

// Text Box: Height
let txtHeight = new TextBox()
txtHeight.Left  <- 72
txtHeight.Top   <- 43
txtHeight.Width <- 54
geometricTriangle.Controls.Add txtHeight

// Button: Calculate
let btnCalculate = new Button()
btnCalculate.Left <- 133
btnCalculate.Top  <-  43
btnCalculate.Text <- "Calculate"

// Label: Area
let lblArea = new Label()
lblArea.Left  <-  22
lblArea.Top   <- 78
lblArea.Width <-  40
lblArea.Text  <- "Area:"
geometricTriangle.Controls.Add lblArea

// Text Box: Area
let txtArea = new TextBox()
txtArea.Left  <- 72
txtArea.Top   <- 75
txtArea.Width <- 84
geometricTriangle.Controls.Add txtArea

// Button: Close
let btnClose = new Button()
btnClose.Left <- 163
btnClose.Top  <- 72
btnClose.Text <- "Close"
let btnCloseClick e = geometricTriangle.Close()
btnClose.Click.Add btnCloseClick
geometricTriangle.Controls.Add btnClose

let btnCalculateClick e =
    let length = float txtBase.Text
    let height = float txtHeight.Text

    let tri = new Triangle(length, height)
    
    // Accessing a read-only property
    let strArea = sprintf "%f" tri.Area
    txtArea.Text <- strArea

btnCalculate.Click.Add btnCalculateClick
geometricTriangle.Controls.Add btnCalculate

do Application.Run geometricTriangle

Here is an example of executing the program:

The Data Type of a Parameter

If you want to specify the data type of the property, after the parentheses of the with get() expression, add a colon and the name of the type. Here is an example:

type Triangle(b, h) =
    member me.Area with get() : float = b * h / 2.00

In most cases, you can omit the data type because F# is an inferred language.

Mutating the Value of a Property

Consider the following program that contains a number-based property:

type Circle(radius) =
    member this.Radius with get() = radius

Instead of passing a value of the constructor directly to a property, you can create a let binding member variable that can act between the constructor and the property. Such a member variable can get the value from the constructor to the property. In this case, assign the argument of the contructor to the let member variable. Then assign that member variable to the property.

To allow the member in the class to be able to change the value of the property, create the local variable as mutable. Here is an example:

type Circle(radius) =
    let mutable r = radius
    member this.Radius with get() = r

This would work the same as previously.

A Read-Only Property With No Body

The F# language provides an even simpler technique to create a read-only property. It consists of simply assigning something to the property. The formula to follow is:

member [ this | Self-Identifier ].property-name : data-type = some-value

You can use an argument passed to a constructor of the class and assign it to the property. Here is an example:

type Circle(radius) =
    member me.Radius = radius
    member this.Diameter with get() = radius * 2.00
    member this.Circumference with get() = this.Diameter * 3.14156
    member this.Area with get() = radius * radius * 3.14156

let round = Circle(48.06)

Write-Only Properties

 

Setting a Value to a Property

A property is referred to as write-only if it can only get a value from outside the class but cannot make that value available to the clients of the class. A write-only property is created using the set() function that takes one argument. The formula to create a write-only property is:

member [ this | self-identifier ].property-name with set(parameter) = body

Start with the member [ this | self-identifier ].property-name section as we described for the read-only property. This is followed by with set(). In its parentheses, enter unit or a name for a parameter. You can then assign a constructor parameter to it. This can be done as follows:

type Circle(radius) =
    member plate.Radius with set(unit) = radius

Mutating the Value of a Read-Only Property

The primary role of a setter is to set (specify or change) the value of a property. One way to control it is to use a mutable let binding member that would control the value of the property. To do this, declare a let mutable member variable and assign an appropriate value to it. Create the property but assign its argument to the mutable member. Here is an example:

type Circle() =
    let mutable rad = 0.00
    member me.Radius with set(value) = rad <- value

To specify the value of the property outside the class, treat the property as a mutable variable. That is, use the <- operator to assign a value to it. Here is an example:

type Circle() =
    let mutable rad = 0.00
    member me.Radius with set(value) = rad <- value
    member this.Diameter with get() = rad * 2.00

let round = Circle()
round.Radius <- 48.06

That is the same approach we have applied to the properties of Windows controls in previous program. Here are examples:

open System
open System.Windows.Forms

let exercise = new Form()
exercise.Text <- "Exercise"

let lblFirstName = new Label()
lblFirstName.Left <- 12
lblFirstName.Top  <- 21
lblFirstName.Text <- "First Name:"
exercise.Controls.Add(lblFirstName)

let txtFirstName = new TextBox()
txtFirstName.Left  <- 112
txtFirstName.Top   <- 18
txtFirstName.Width <- 88
exercise.Controls.Add(txtFirstName)

Application.Run(exercise)

Remember that a write-only property can only receive a value. This means that you cannot retrieve its value outside the class.

Read/Write Properties

 

Introduction

A property can be used both to write a value and/or to read one from/to a class. Such a property is referred to as read-write. The formula to create a read-write property is a combination of those we used already:

member [ this | Self-Identifier | Class-Name ].Property-Name
    with get() : Data-Type = Some-Value
    and set(Parameter) = body

The new addition to this formula is the and keyword. While following the rules we reviewed for the other sections, the value you assign to the getter (Some-Value) is the same used in the setter. Outside the class, you can create an object and access the property to both assign a value to use and to get its value.

Mutating a Read-Write Property

If you want a property whose value can be changed outside the class, you should use an intermediary mutable member. As seen in previous sections, you have two options. You can add a let mutable member. Here an example:

type Circle() =
    let mutable radius = 0.00
    member me.Radius with get() = radius and set(value) = radius <- value

By the way, if the whole code of the property can fit in one line, create it on a line. Here are examples:

open System
open System.Windows.Forms

type StoreItem(number, make, category, subcategory, name, size, price, discrate) =
    let mutable nbr = number
    let mutable mk = make
    let mutable cat = category
    let mutable sub = subcategory
    let mutable nm = name
    let mutable sz = size
    let mutable prc = price
    let mutable rate = discrate

    member this.ItemNumber   with get() = nbr  and set(value) = nbr  <- value
    member this.Manufacturer with get() = mk   and set(value) = mk   <- value
    member this.Category     with get() = cat  and set(value) = cat  <- value
    member this.SubCategory  with get() = sub  and set(value) = sub  <- value
    member this.ItemName     with get() = nm   and set(value) = nm   <- value
    member this.ItemSize     with get() = sz   and set(value) = sz   <- value
    member this.UnitPrice    with get() = prc  and set(value) = prc  <- value
    member this.DiscountRate with get() = rate and set(value) = rate <- value
    member this.DiscountAmount with get() = this.UnitPrice * this.DiscountRate / 100.00
    member this.MarkedPrice with get() = this.UnitPrice - this.DiscountAmount
    new(number, make, category, subcategory, name, size, price) = StoreItem(number, make, category, subcategory, name, size, price, 0.00) 

let si = new StoreItem("927497", "Guess", "Women", "Dresses", "Zip Front Fit and Flare Dressr", "4", 135.75, float 25)

// Form: Department Store
let departmentStore = new Form()
departmentStore.Width  <- 390
departmentStore.Height <- 238
departmentStore.Text   <- "Department Store"

// Label: Item Number
let lblItemNumber = new Label()
lblItemNumber.Left   <- 22
lblItemNumber.Top    <- 18
lblItemNumber.Width  <- 74
lblItemNumber.Text   <- "Item #:"
departmentStore.Controls.Add lblItemNumber

// Text Box: Item Number
let txtItemNumber = new TextBox()
txtItemNumber.Left  <- 101
txtItemNumber.Top   <- 15
txtItemNumber.Width <- 64
txtItemNumber.Text   <- si.ItemNumber
departmentStore.Controls.Add txtItemNumber

// Label: Manufacturer
let lblManufacturer = new Label()
lblManufacturer.Left  <- 22
lblManufacturer.Top   <- 44
lblManufacturer.Width <- 74
lblManufacturer.Text  <- "Manufacturer:"
departmentStore.Controls.Add lblManufacturer

// Text Box: Manufacturer
let txtManufacturer = new TextBox()
txtManufacturer.Left  <- 101
txtManufacturer.Top   <- 41
txtManufacturer.Width <- 262
txtManufacturer.Text  <- si.Manufacturer
departmentStore.Controls.Add txtManufacturer

// Label: Category
let lblCategory = new Label()
lblCategory.Left  <- 22
lblCategory.Top   <- 70
lblCategory.Width <- 52
lblCategory.Text  <- "Category:"
departmentStore.Controls.Add lblCategory

// Text Box: Category
let txtCategory = new TextBox()
txtCategory.Left  <- 101
txtCategory.Top   <- 67
txtCategory.Width <- 87
txtCategory.Text  <- si.Category
departmentStore.Controls.Add txtCategory

// Label: Sub-Category
let lblSubCategory = new Label()
lblSubCategory.Left  <- 194
lblSubCategory.Top   <- 70
lblSubCategory.Width <- 78
lblSubCategory.Text  <- "Sub-Category:"
departmentStore.Controls.Add lblSubCategory

// Text Box: Sub-Category
let txtSubCategory = new TextBox()
txtSubCategory.Left  <- 274
txtSubCategory.Top   <-  67
txtSubCategory.Width <-  90
txtSubCategory.Text  <- si.SubCategory
departmentStore.Controls.Add txtSubCategory

// Label: Item Name
let lblItemName = new Label()
lblItemName.Left  <- 22
lblItemName.Top   <- 96
lblItemName.Width <- 68
lblItemName.Text <- "Item Name:"
departmentStore.Controls.Add lblItemName

// Text Box: Item Name
let txtItemName = new TextBox()
txtItemName.Left  <- 101
txtItemName.Top   <-  93
txtItemName.Width <- 262
txtItemName.Text  <- si.ItemName
departmentStore.Controls.Add txtItemName

// Label: Item Size
let lblItemSize = new Label()
lblItemSize.Left  <-  22
lblItemSize.Top   <- 122
lblItemSize.Width <-  64
lblItemSize.Text  <- "Item Size:"
departmentStore.Controls.Add lblItemSize

// Text Box: Item Size
let txtItemSize = new TextBox()
txtItemSize.Left  <- 101
txtItemSize.Top   <- 119
txtItemSize.Width <-  87
txtItemSize.Text  <- si.ItemSize
departmentStore.Controls.Add txtItemSize

// Label: Unit Price
let lblUnitPrice = new Label()
lblUnitPrice.Left  <- 194
lblUnitPrice.Top   <- 122
lblUnitPrice.Width <-  75
lblUnitPrice.Text <- "Unit Price:"
departmentStore.Controls.Add lblUnitPrice

// Text Box: Unit Price
let txtUnitPrice = new TextBox()
txtUnitPrice.Left  <- 288
txtUnitPrice.Top   <- 119
txtUnitPrice.Width <-  75
txtUnitPrice.Text  <- sprintf "%g" si.UnitPrice
departmentStore.Controls.Add txtUnitPrice

// Label: Discount Rate
let lblDiscountRate = new Label()
lblDiscountRate.Left  <-  22
lblDiscountRate.Top   <- 148
lblDiscountRate.Width <-  78
lblDiscountRate.Text <- "Discount Rate:"
departmentStore.Controls.Add lblDiscountRate

// Text Box: Discount Rate
let txtDiscountRate = new TextBox()
txtDiscountRate.Left  <- 101
txtDiscountRate.Top   <- 145
txtDiscountRate.Width <-  87
txtDiscountRate.Text  <- (sprintf "%g%c" si.DiscountRate '%')
departmentStore.Controls.Add txtDiscountRate

// Label: Discount Amount
let lblDiscountAmount = new Label()
lblDiscountAmount.Left  <- 194
lblDiscountAmount.Top   <- 148
lblDiscountAmount.Width <-  94
lblDiscountAmount.Text  <- "Discount Amount:"
departmentStore.Controls.Add lblDiscountAmount

// Text Box: Discount Amount
let txtDiscountAmount = new TextBox()
txtDiscountAmount.Left  <- 288
txtDiscountAmount.Top   <- 145
txtDiscountAmount.Width <-  75
txtDiscountAmount.Text  <- sprintf "%0.02f" si.DiscountAmount
departmentStore.Controls.Add txtDiscountAmount

// Label: Marked Price
let lblMarkedPrice = new Label()
lblMarkedPrice.Left  <-  22
lblMarkedPrice.Top   <- 174
lblMarkedPrice.Width <-  77
lblMarkedPrice.Text  <- "Marked Price:"
departmentStore.Controls.Add lblMarkedPrice

// Text Box: MarkedPrice
let txtMarkedPrice = new TextBox()
txtMarkedPrice.Left  <- 101
txtMarkedPrice.Top   <- 171
txtMarkedPrice.Width <-  87
txtMarkedPrice.Text  <- sprintf "%0.02f" si.MarkedPrice
departmentStore.Controls.Add txtMarkedPrice

// Button: Close
let btnClose = new Button()
btnClose.Left <- 288
btnClose.Top  <- 174
btnClose.Text <- "Close"
let btnCloseClick e = departmentStore.Close()
btnClose.Click.Add btnCloseClick
departmentStore.Controls.Add btnClose

do Application.Run departmentStore

This would produce:

Mutating a Read-Write Property

After associating a property to a mutable member, to specify or change the value of the property outside the class, use the <- operator. Here is an example:

open System
open System.Windows.Forms

type HexagonalDipyramid(side, height) =
    let mutable s = side
    let mutable h = height
    
    member this.Side with get()   = s and set(value) = s <- value
    member this.Height with get() = h and set(value) = h <- value
    member this.Volume with get() = s * s * h * (sqrt 3.00)
    new() = HexagonalDipyramid(0.00, 0.00)

// Form: Hexagonal Dipyramid
let hexagonal = new Form()
hexagonal.Width  <- 265
hexagonal.Height <- 138
hexagonal.Text <- "Hexagonal Dipyramid"

let calculateVolume (b : float) (h : float) = b * h / 2.00

// Label: Side
let lblSide = new Label()
lblSide.Left   <- 22
lblSide.Top    <- 19
lblSide.Width  <- 40
lblSide.Text   <- "Side:"
hexagonal.Controls.Add lblSide

// Text Box: Side
let txtSide = new TextBox()
txtSide.Left  <- 72
txtSide.Top   <- 16
txtSide.Width <- 54
hexagonal.Controls.Add txtSide

// Label: Height
let lblHeight = new Label()
lblHeight.Left <- 22
lblHeight.Top <- 50
lblHeight.Width <- 45
lblHeight.Text <- "Height:"
hexagonal.Controls.Add lblHeight

// Text Box: Height
let txtHeight = new TextBox()
txtHeight.Left  <- 72
txtHeight.Top   <- 43
txtHeight.Width <- 54
hexagonal.Controls.Add txtHeight

// Button: Calculate
let btnCalculate = new Button()
btnCalculate.Left <- 164
btnCalculate.Top  <-  43
btnCalculate.Text <- "Calculate"

// Label: Volume
let lblVolume = new Label()
lblVolume.Left  <-  22
lblVolume.Top   <- 78
lblVolume.Width <-  48
lblVolume.Text  <- "Volume:"
hexagonal.Controls.Add lblVolume

// Text Box: Volume
let txtVolume = new TextBox()
txtVolume.Left  <- 72
txtVolume.Top   <- 75
txtVolume.Width <- 84
hexagonal.Controls.Add txtVolume

// Button: Close
let btnClose = new Button()
btnClose.Left <- 164
btnClose.Top  <- 72
btnClose.Text <- "Close"
let btnCloseClick e = hexagonal.Close()
btnClose.Click.Add btnCloseClick
hexagonal.Controls.Add btnClose

let btnCalculateClick e =
    let sd = float txtSide.Text
    let hgt = float txtHeight.Text

    let hd = new HexagonalDipyramid()
    hd.Side <- sd
    hd.Height <- hgt

    // Accessing a read-only property
    let strVolume = sprintf "%f" hd.Volume
    txtVolume.Text <- strVolume

btnCalculate.Click.Add btnCalculateClick
hexagonal.Controls.Add btnCalculate

do Application.Run hexagonal

Here is an example of running the program:

Mutating a Read-Write Property Mutating a Read-Write Property

If necessary, you can use many lines to create a property. In this case, you can add the with get() ... section on its own line and the setter, if available, on its own line. Remember to indent. Here are examples:

type Circle() =
    let mutable radius = 0.00
    member me.Radius
        with get() = radius
        and set(value) = radius <- value
    member this.Diameter with get() = radius * 2.00
    member this.Circumference
        with get() = this.Diameter * 3.14156
    member this.Area with get() = radius * radius * 3.14156

In the same way, you can create as many read-write properties as you judge necessary. Here are examples:

The above formula allows you to create a read-write property in one block. As an alternative, you can create a  read-write property in two different sections. The read property would be created in its own section and the write property in its section. Here is an example:

type Vehicle() =
    let mutable tagNbr = string
    member this.TagNumber with get() = tagNbr
    member this.TagNumber with set(value) = tagNbr <- value

Automatic Properties

The F# (like the C#) language, provides an easy and fast way to create a read-write property. To create such a property, replace the this keyword or the self-identifier with the val keyword, assign the default value to the name of the property, and use the with get, set clause in place of defining the separate sections of the property. The formula to follow is:

member val PropertyName = DefautValue = with get, set

Here is an example:

type Vehicle() =
    member val TagNumber = "" with get, set

A better alternative is to assign a constructor parameter to the property name. Here is an example:

type Vehicle(tag) =
    member val TagNumber = tag with get, set

After defining the property, you can use it as you see fit, such as displaying its value outside the class. Here is an example:

type Vehicle(tag) =
    member val TagNumber = tag with get, set

// Accessing the property
sprintf "%s" small.TagNumber
     
 

Data Types and Properties

 

Properties of Primitive Types

Because F# is an inferred language, the compiler works tremendously behind the scenes to find out the appropriate data type to apply to a variable or to a property if a data type is not specified. Still, when creating a property, you may prefer to specify the desired data type. As a reminder, the formula to create a read-only property is:

member [ this | self-identifier | class-name ].property-name with get() : data-type = some-value

In this case, specify the data type after the with get() expression. Here is an example:

type Circle(radius) =
    let r = radius
    member Circle.Radius with get() : double = r

In a write-only property, you specify the data type on the parameter of the set() member. The formula to follow is:

member [ this | self-identifier | class-name ].property-name with set(parameter : data-type) = body

Here is an example:

type Circle() =
    let mutable rad : double = 0.00
    member me.Radius with set(value : double) = rad <- value

To specify the data type of a read-write property, the formula to follow is:

member [ this | self-identifier | class-name ].property-name
    with get() : data-type = some-value
    and set(Parameter : data-type) = body

You can specify the data type of the getter only, to the setter only, or to both. Here are examples:

type Vehicle(tag : string, make : string, model : string, year : int) =
    let mutable tagNbr = tag
    let mutable manufacturer = make
    let mutable mdl = model
    let mutable yr = year
    member this.TagNumber
        with get() : string = tagNbr
        and set(value : string) = tagNbr <- value
    member this.Make
        with get() : string = manufacturer
        and set(value : string) = manufacturer <- value
    member this.Model
        with get() : string = mdl
        and set(value : string) = mdl <- value
    member this.Year
        with get() : int = yr
        and set(value : int) = yr <- value
    new() = Vehicle("", "", "", 1960)

A Boolean Property

As done for strings or numbers, a property can use Boolean values, either to read values, to write values, or to do both. The rules to create the property are the same as we have seen so far, except that the property can use only one of two values: true or false. Here is an example:

open System
open System.Windows.Forms

type HexagonalDipyramid(side, height, is) =
    let mutable s = side
    let mutable h = height
    let mutable i = is
    
    member this.Side with get()   = s and set(value) = s <- value
    member this.Height with get() = h and set(value) = h <- value
    member this.IsRegularVolume with get() = i and set(value) = i <- value
    member this.Volume with get() = s * s * h * (sqrt 3.00)
    new() = HexagonalDipyramid(0.00, 0.00, true)

// Form: Hexagonal Dipyramid
let hexagonal = new Form()
hexagonal.Width  <- 265
hexagonal.Height <- 138
hexagonal.Text <- "Hexagonal Dipyramid"

let calculateVolume (b : float) (h : float) = b * h / 2.00

// Label: Side
let lblSide = new Label()
lblSide.Left   <- 22
lblSide.Top    <- 19
lblSide.Width  <- 40
lblSide.Text   <- "Side:"
hexagonal.Controls.Add lblSide

// Text Box: Side
let txtSide = new TextBox()
txtSide.Left  <- 72
txtSide.Top   <- 16
txtSide.Width <- 54
hexagonal.Controls.Add txtSide

// Label: Height
let lblHeight = new Label()
lblHeight.Left <- 22
lblHeight.Top <- 50
lblHeight.Width <- 45
lblHeight.Text <- "Height:"
hexagonal.Controls.Add lblHeight

// Text Box: Height
let txtHeight = new TextBox()
txtHeight.Left  <- 72
txtHeight.Top   <- 43
txtHeight.Width <- 54
hexagonal.Controls.Add txtHeight

// Button: Calculate
let btnCalculate = new Button()
btnCalculate.Left <- 164
btnCalculate.Top  <-  43
btnCalculate.Text <- "Calculate"

// Label: Volume
let lblVolume = new Label()
lblVolume.Left  <-  22
lblVolume.Top   <- 78
lblVolume.Width <-  48
lblVolume.Text  <- "Volume:"
hexagonal.Controls.Add lblVolume

// Text Box: Volume
let txtVolume = new TextBox()
txtVolume.Left  <- 72
txtVolume.Top   <- 75
txtVolume.Width <- 84
hexagonal.Controls.Add txtVolume

// Button: Close
let btnClose = new Button()
btnClose.Left <- 164
btnClose.Top  <- 72
btnClose.Text <- "Close"
let btnCloseClick e = hexagonal.Close()
btnClose.Click.Add btnCloseClick
hexagonal.Controls.Add btnClose

let btnCalculateClick e =
    let sd = float txtSide.Text
    let hgt = float txtHeight.Text

    let hd = new HexagonalDipyramid()
    hd.Side <- sd
    hd.Height <- hgt
    hd.IsRegularVolume <- true

    assert(hd.IsRegularVolume)

    let strVolume = sprintf "%f" hd.Volume
    txtVolume.Text <- strVolume

btnCalculate.Click.Add btnCalculateClick
hexagonal.Controls.Add btnCalculate

do Application.Run hexagonal

This would produce an error (because the assert() function produces an error).

Enumerated Properties

As an enumeration can be used as a data type for variables and parameters, it can be used for a property.u can create an enumeration that is of the type of an enumeration. Normally, because F# is an inferred language, when creating the class, you don't have to specify the type of the property that uses an enumeration. Here is an example:

type Triangle(b, h, c) =
    let mutable bs = b
    let mutable hgt = h
    let mutable cat = c
    member this.Base with get() = bs and set(value) = bs <- value
    member this.Height with get() = hgt and set(value) = hgt <- value
    member this.Category with get() = cat and set(value) = cat <- value

When you create an object, you can then pass a member of the enumeration where the property is used. Here is an example:

open System
open System.Windows.Forms

type TriangleType =
| Right
| Equilateral
| Isosceles
| Obtuse
| Acute

type Triangle(b, h, c) =
    let mutable bs = b
    let mutable hgt = h
    let mutable cat = c
    member this.Base with get() = bs and set(value) = bs <- value
    member this.Height with get() = hgt and set(value) = hgt <- value
    member this.Category with get() = cat and set(value) = cat <- value

    // A read-only property
    member me.Area with get() = b * h / 2.00

// Form: Geometric Triangle
let geometricTriangle = new Form()
geometricTriangle.Width  <- 225
geometricTriangle.Height <- 168
geometricTriangle.Text <- "Triangle"

// Label: Base
let lblBase = new Label()
lblBase.Left   <- 22
lblBase.Top    <- 19
lblBase.Width  <- 40
lblBase.Text   <- "Base:"
geometricTriangle.Controls.Add lblBase

// Text Box: Base
let txtBase = new TextBox()
txtBase.Left  <- 82
txtBase.Top   <- 16
txtBase.Width <- 54
txtBase.Text  <- "36.84"
geometricTriangle.Controls.Add txtBase

// Label: Height
let lblHeight = new Label()
lblHeight.Left  <- 22
lblHeight.Top   <- 50
lblHeight.Width <- 45
lblHeight.Text  <- "Height:"
geometricTriangle.Controls.Add lblHeight

// Text Box: Height
let txtHeight = new TextBox()
txtHeight.Left  <- 82
txtHeight.Top   <- 43
txtHeight.Width <- 54
txtHeight.Text  <- "26.74"
geometricTriangle.Controls.Add txtHeight

// Label: Summary
let lblSummary = new Label()
lblSummary.Left  <-  22
lblSummary.Top   <- 78
lblSummary.Width <-  60
lblSummary.Text  <- "Summary:"
geometricTriangle.Controls.Add lblSummary

// Text Box: Summary
let txtSummary = new TextBox()
txtSummary.Left  <- 82
txtSummary.Top   <- 75
txtSummary.Width <- 122
geometricTriangle.Controls.Add txtSummary

// Button: Close
let btnClose = new Button()
btnClose.Left <- 22
btnClose.Top  <- 105
btnClose.Width  <- 182
btnClose.Text <- "Close"
let btnCloseClick e = geometricTriangle.Close()
btnClose.Click.Add btnCloseClick
geometricTriangle.Controls.Add btnClose

let x = float txtBase.Text
let y = float txtHeight.Text

let tri = new Triangle(x, y, Isosceles)
let summary = sprintf "%f (%A)" tri.Area tri.Category
txtSummary.Text <- summary

do Application.Run geometricTriangle

Based on this, it may be a good idea to specify the data type of a property that is based on an enumeration. Here is an example:

type TriangleType =
| Right
| Equilateral
| Isosceles
| Obtuse
| Acute

type Triangle(b : float, h : float, c : TriangleType) =
    let mutable bs : float = b
    let mutable hgt : float = h
    let mutable cat : TriangleType = c
    member this.Base with get() : float = bs and set(value : float) = bs <- value
    member this.Height with get() : float = hgt and set(value : float) = hgt <- value
    member this.Category with get() : TriangleType = cat and set(value : TriangleType) = cat <- value

    // A read-only property
    member me.Area with get() = b * h / 2.00

A Property From a Class

As a class can be made a member variable of another class, it can also be created as a property. The formula is the exact same we have seen for properties of primitive types. If the class that represents the property has an appropriate constructor, you can pass a parameter of that class to the constructor of your new class and use that argument in the property.

Options on Creating and Using Properties

 

Properties and Fields

There are so many ways to create properties. Each technique presents its characteristics (or advantages and disadvantages).

In the body of a class, you can add members named fields that created like variables. For such a field, you can assign a constant value or you can assign an expression. Here are examples:

open System
open System.Windows.Forms

type RentalOrder(days, DailyRateApplied, taxRate) =
    member this.SubTotal = float days * DailyRateApplied
    member this.TaxAmount = this.SubTotal * taxRate / 100.00
    member this.InvoiceAmount = this.SubTotal + this.TaxAmount

// Form: Car Rental Company
let carRental = new Form()
carRental.Width  <- 225
carRental.Height <- 250
carRental.Text <- "Car Rental Company"

// Label: Days Used
let lblDaysUsed = new Label()
lblDaysUsed.Left   <- 18
lblDaysUsed.Top    <- 21
lblDaysUsed.Width  <- 80
lblDaysUsed.Text   <- "Days Used:"
carRental.Controls.Add lblDaysUsed

// Text Box: Days Used
let txtDaysUsed = new TextBox()
txtDaysUsed.Left  <- 125
txtDaysUsed.Top   <-  18
txtDaysUsed.Width <-  46
txtDaysUsed.Text  <- "1"
carRental.Controls.Add txtDaysUsed

// Label: Daily Rate Applied
let lblDailyRateApplied = new Label()
lblDailyRateApplied.Left   <- 18
lblDailyRateApplied.Top    <- 48
lblDailyRateApplied.Width  <- 102
lblDailyRateApplied.Text   <- "Daily Rate Applied:"
carRental.Controls.Add lblDailyRateApplied

// Text Box: Daily Rate Applied
let txtDailyRateApplied = new TextBox()
txtDailyRateApplied.Left  <- 125
txtDailyRateApplied.Top   <-  45
txtDailyRateApplied.Width <-  75
txtDailyRateApplied.Text  <- "0.00"
carRental.Controls.Add txtDailyRateApplied

// Label: Tax Rate
let lblTaxRate = new Label()
lblTaxRate.Left   <- 18
lblTaxRate.Top    <- 73
lblTaxRate.Width  <- 85
lblTaxRate.Text   <- "Tax Rate:"
carRental.Controls.Add lblTaxRate

// Text Box: Tax Rate
let txtTaxRate = new TextBox()
txtTaxRate.Left  <- 125
txtTaxRate.Top   <-  70
txtTaxRate.Width <-  45
txtTaxRate.Text  <- "7.50"
carRental.Controls.Add txtTaxRate

// Label: %
let lblPercent = new Label()
lblPercent.Left   <- 170
lblPercent.Top    <- 73
lblPercent.Width  <- 85
lblPercent.Text   <- "%"
carRental.Controls.Add lblPercent

// Label: Sub-Total
let lblSubTotal = new Label()
lblSubTotal.Left  <-  18
lblSubTotal.Top   <- 132
lblSubTotal.Width <-  80
lblSubTotal.Text  <- "Sub-Total:"
carRental.Controls.Add lblSubTotal

// Text Box: Sub-Total
let txtSubTotal = new TextBox()
txtSubTotal.Left  <- 125
txtSubTotal.Top   <- 130
txtSubTotal.Width <-  75
txtSubTotal.Text  <- "0.00"
carRental.Controls.Add txtSubTotal

// Label: Tax Amount
let lblTaxAmount = new Label()
lblTaxAmount.Left  <-  18
lblTaxAmount.Top   <- 158
lblTaxAmount.Width <-  80
lblTaxAmount.Text  <- "Tax Amount:"
carRental.Controls.Add lblTaxAmount

// Text Box:Tax  Amount
let txtTaxAmount = new TextBox()
txtTaxAmount.Left  <- 125
txtTaxAmount.Top   <- 155
txtTaxAmount.Width <-  75
txtTaxAmount.Text  <- "0.00"
carRental.Controls.Add txtTaxAmount

// Label: Net Total
let lblNetTotal = new Label()
lblNetTotal.Left  <-  18
lblNetTotal.Top   <- 186
lblNetTotal.Width <-  80
lblNetTotal.Text  <- "Net Total:"
carRental.Controls.Add lblNetTotal

// Text Box: Net Total
let txtNetTotal = new TextBox()
txtNetTotal.Left  <- 125
txtNetTotal.Top   <- 181
txtNetTotal.Width <-  75
txtNetTotal.Text  <- "0.00"
carRental.Controls.Add txtNetTotal

// Button: Calculate
let btnCalculate = new Button()
btnCalculate.Left <- 125
btnCalculate.Top  <- 100
btnCalculate.Text <- "Calculate"

let btnCalculateClick e =
    let days = float txtDaysUsed.Text
    let rateApplied = float txtDailyRateApplied.Text
    let taxRate = float txtTaxRate.Text

    let ro = new RentalOrder(days, rateApplied, taxRate)

    let strSubTotal = sprintf "%0.02f" ro.SubTotal
    let strTaxAmount = sprintf "%0.02f" ro.TaxAmount
    let strTotal = sprintf "%0.02f" ro.InvoiceAmount
    
    txtSubTotal.Text  <- strSubTotal
    txtTaxAmount.Text <- strTaxAmount
    txtNetTotal.Text  <- strTotal

btnCalculate.Click.Add btnCalculateClick
carRental.Controls.Add btnCalculate

do Application.Run carRental

Here is an example of running the program:

Different Techniques of Creating Properties Different Techniques of Creating Properties

Properties and Conditional Statements

When defining the behavior of a property, you can use a conditional statement to make some validations in order to specify the right value for the property. Here is an example:

open System
open System.Windows.Forms

type CustomerInvoice(consumption, txRate, discRate) =
    let mutable tx = txRate
    let mutable disc = if discRate > 0.00 then discRate else 0.00

    member this.TotalCharges
        with get() =
            if consumption <= 1.3779 then
                consumption * 45.50
            elif consumption <= 0.9607 then
                consumption * 84.65
            elif consumption <= 0.5258 then
                consumption * 112.85
            else
                consumption * 122.74
            
    member this.DiscountAmount with get() = this.TotalCharges * disc / 100.00
    member this.TaxAmount
        with get() =
            let priceAfterDiscountRate = this.TotalCharges - this.DiscountAmount
            priceAfterDiscountRate * tx / 100.00
    member this.TotalInvoice with get() = this.TotalCharges + this.TaxAmount

// Form: Gas Utility Company
let gasUtilityCompany = new Form()
gasUtilityCompany.Width  <- 208
gasUtilityCompany.Height <- 280
gasUtilityCompany.Text <- "Gas Utility Company"

// Label: CCF Consumption
let lblConsumption = new Label()
lblConsumption.Left   <- 18
lblConsumption.Top    <- 21
lblConsumption.Width  <- 80
lblConsumption.Text   <- "Consumption:"
gasUtilityCompany.Controls.Add lblConsumption

// Text Box: Days Used
let txtConsumption = new TextBox()
txtConsumption.Left  <- 115
txtConsumption.Top   <-  18
txtConsumption.Width <-  46
txtConsumption.Text  <- "0.00"
gasUtilityCompany.Controls.Add txtConsumption

// Label: Discount Rate
let lblDiscountRate = new Label()
lblDiscountRate.Left   <- 18
lblDiscountRate.Top    <- 48
lblDiscountRate.Width  <- 85
lblDiscountRate.Text   <- "Discount Rate:"
gasUtilityCompany.Controls.Add lblDiscountRate

// Text Box: Daily Rate Applied
let txtDiscountRate = new TextBox()
txtDiscountRate.Left  <- 115
txtDiscountRate.Top   <-  45
txtDiscountRate.Width <-  45
txtDiscountRate.Text  <- "0.00"
gasUtilityCompany.Controls.Add txtDiscountRate

// Label: Discount %
let lblDiscountPercent = new Label()
lblDiscountPercent.Left   <- 160
lblDiscountPercent.Top    <- 49
lblDiscountPercent.Width  <- 85
lblDiscountPercent.Text   <- "%"
gasUtilityCompany.Controls.Add lblDiscountPercent

// Label: Tax Rate
let lblTaxRate = new Label()
lblTaxRate.Left   <- 18
lblTaxRate.Top    <- 78
lblTaxRate.Width  <- 85
lblTaxRate.Text   <- "Tax Rate:"
gasUtilityCompany.Controls.Add lblTaxRate

// Text Box: Tax Rate
let txtTaxRate = new TextBox()
txtTaxRate.Left  <- 115
txtTaxRate.Top   <-  74
txtTaxRate.Width <-  45
txtTaxRate.Text  <- "6.75"
gasUtilityCompany.Controls.Add txtTaxRate

// Label: Tax %
let lblTaxPercent = new Label()
lblTaxPercent.Left   <- 160
lblTaxPercent.Top    <- 78
lblTaxPercent.Width  <- 85
lblTaxPercent.Text   <- "%"
gasUtilityCompany.Controls.Add lblTaxPercent

// Label: Sub-Total
let lblTotalCharges = new Label()
lblTotalCharges.Left  <-  18
lblTotalCharges.Top   <- 134
lblTotalCharges.Width <-  80
lblTotalCharges.Text  <- "Total Charges:"
gasUtilityCompany.Controls.Add lblTotalCharges

// Text Box: Sub-Total
let txtTotalCharges = new TextBox()
txtTotalCharges.Left  <- 115
txtTotalCharges.Top   <- 132
txtTotalCharges.Width <-  70
txtTotalCharges.Text  <- "0.00"
gasUtilityCompany.Controls.Add txtTotalCharges

// Label: Discount Amount
let lblDiscountAmount = new Label()
lblDiscountAmount.Left  <-  18
lblDiscountAmount.Top   <- 160
lblDiscountAmount.Width <-  95
lblDiscountAmount.Text  <- "Discount Amount:"
gasUtilityCompany.Controls.Add lblDiscountAmount

// Text Box: Discount  Amount
let txtDiscountAmount = new TextBox()
txtDiscountAmount.Left  <- 115
txtDiscountAmount.Top   <- 158
txtDiscountAmount.Width <-  70
txtDiscountAmount.Text  <- "0.00"
gasUtilityCompany.Controls.Add txtDiscountAmount

// Label: Tax Amount
let lblTaxAmount = new Label()
lblTaxAmount.Left  <-  18
lblTaxAmount.Top   <- 184
lblTaxAmount.Width <-  80
lblTaxAmount.Text  <- "Tax Amount:"
gasUtilityCompany.Controls.Add lblTaxAmount

// Text Box:Tax  Amount
let txtTaxAmount = new TextBox()
txtTaxAmount.Left  <- 115
txtTaxAmount.Top   <- 184
txtTaxAmount.Width <-  70
txtTaxAmount.Text  <- "0.00"
gasUtilityCompany.Controls.Add txtTaxAmount

// Label: Amount to Pay
let lblAmountDue = new Label()
lblAmountDue.Left  <-  18
lblAmountDue.Top   <- 212
lblAmountDue.Width <-  80
lblAmountDue.Text  <- "Amount Due:"
gasUtilityCompany.Controls.Add lblAmountDue

// Text Box: Amount to Pay
let txtAmountDue = new TextBox()
txtAmountDue.Left  <- 115
txtAmountDue.Top   <- 210
txtAmountDue.Width <-  70
txtAmountDue.Text  <- "0.00"
gasUtilityCompany.Controls.Add txtAmountDue

// Button: Calculate
let btnCalculate = new Button()
btnCalculate.Left  <- 115
btnCalculate.Top   <- 102
btnCalculate.Width <-  70
btnCalculate.Text  <- "Calculate"

let btnCalculateClick e =
    let consumption = float txtConsumption.Text
    let taxRate = float txtTaxRate.Text
    let discRate = float txtDiscountRate.Text

    let bill = new CustomerInvoice(consumption, taxRate, discRate)

    let strTotalCharges = sprintf "%0.02f" bill.TotalCharges
    let strTaxAmount = sprintf "%0.02f" bill.TaxAmount
    let strTotal = sprintf "%0.02f" bill.TotalInvoice
    
    txtTotalCharges.Text  <- strTotalCharges
    txtTaxAmount.Text <- strTaxAmount
    txtAmountDue.Text  <- strTotal

btnCalculate.Click.Add btnCalculateClick
gasUtilityCompany.Controls.Add btnCalculate

do Application.Run gasUtilityCompany

Here is an example of running the program:

Different Techniques of Creating Properties Different Techniques of Creating Properties Different Techniques of Creating Properties

Properties and Functions

You can use the body of a property to create as many statements as possible. As we had seen with functions, you can call functions in the body of a property in order to specify what value the property should have. Here is an example:

open System
open System.Windows.Forms

let add a b = a + b
let subtract a b = a - b
let multiply a b = a * b
let divide a b = if b <> 0.00 then a / b else 0.00
let PI = 3.14156

type Ellipse(small, large) = // r and R
    let mutable r = small
    let mutable R = large
    
    member this.SmallRadius with get() = r
    member this.LargeRadius with get() = R
    member this.Area = (multiply R r) * 3.14156
    member this.Circumference =
        let RMinusrSquared = multiply (subtract R r) (subtract R r)
        let RPlusrSquared = multiply (add R r) (add R r)
        let squareRooted = sqrt(-3.00 * RMinusrSquared / RPlusrSquared + 4.00)
        let denominator = RPlusrSquared * (squareRooted + 10.00)
        PI * (add R r) * (3.00 * (divide RMinusrSquared denominator) + 1.00)

// Form: Geometric Ellipse
let geometry = new Form()
geometry.Width  <- 290
geometry.Height <- 170
geometry.Text <- "Geometric Ellipse"

// Label: Small Radius
let lblSmallRadius = new Label()
lblSmallRadius.Left   <- 22
lblSmallRadius.Top    <- 19
lblSmallRadius.Width  <- 80
lblSmallRadius.Text   <- "Small Radius:"
geometry.Controls.Add lblSmallRadius

// Text Box: Small Radius
let txtSmallRadius = new TextBox()
txtSmallRadius.Left  <- 106
txtSmallRadius.Top   <-  16
txtSmallRadius.Width <-  75
txtSmallRadius.Text  <- "0.00"
geometry.Controls.Add txtSmallRadius

// Label: Large Radius
let lblLargeRadius = new Label()
lblLargeRadius.Left   <- 22
lblLargeRadius.Top    <- 45
lblLargeRadius.Width  <- 80
lblLargeRadius.Text   <- "Large Radius:"
geometry.Controls.Add lblLargeRadius

// Text Box: Large Radius
let txtLargeRadius = new TextBox()
txtLargeRadius.Left  <- 106
txtLargeRadius.Top   <-  41
txtLargeRadius.Width <-  75
txtLargeRadius.Text  <- "0.00"
geometry.Controls.Add txtLargeRadius

// Label: Area
let lblArea = new Label()
lblArea.Left   <- 22
lblArea.Top    <- 78
lblArea.Width  <- 80
lblArea.Text   <- "Area:"
geometry.Controls.Add lblArea

// Text Box: Area
let txtArea = new TextBox()
txtArea.Left  <- 106
txtArea.Top   <-  75
txtArea.Width <-  75
txtArea.Text  <- "0.00"
geometry.Controls.Add txtArea

// Label: Circumference
let lblCircumference = new Label()
lblCircumference.Left  <-  22
lblCircumference.Top   <- 103
lblCircumference.Width <-  82
lblCircumference.Text  <- "Circumference:"
geometry.Controls.Add lblCircumference

// Text Box: Circumference
let txtCircumference = new TextBox()
txtCircumference.Left  <- 106
txtCircumference.Top   <- 100
txtCircumference.Width <-  75
txtCircumference.Text  <- "0.00"
geometry.Controls.Add txtCircumference

// Button: Calculate
let btnCalculate = new Button()
btnCalculate.Left  <- 197
btnCalculate.Top   <- 40
btnCalculate.Text  <- "Calculate"

let btnCalculateClick e =
    let radius = float txtSmallRadius.Text
    let Radius = float txtLargeRadius.Text

    let oval = new Ellipse(radius, Radius)

    let strCircumference = sprintf "%f" oval.Circumference
    let strArea = sprintf "%f" oval.Area
    
    txtCircumference.Text  <- strCircumference
    txtArea.Text <- strArea

btnCalculate.Click.Add btnCalculateClick
geometry.Controls.Add btnCalculate

// Button: Close
let btnClose = new Button()
btnClose.Left  <- 197
btnClose.Top   <- 98
btnClose.Text  <- "Close"

let btnCloseClick e = geometry.Close()
btnClose.Click.Add btnCloseClick
geometry.Controls.Add btnClose

do Application.Run geometry

Here is an example of running the program:

Properties and Functions

Properties as Named Parameters

After creating one or more read-write properties in a class, you can access them outside the class to initialize them or get their values. Here are examples:

type Vehicle() =
    let mutable tag = ""
    let mutable mk = ""
    let mutable mdl = ""
    let mutable yr = 0
    
    member this.TagNumber with get() = tag and set(value) = tag <- value
    member this.Make with get() = mk and set(value) = mk <- value
    member this.Model with get() = mdl and set(value) = mdl <- value
    member this.Year with get() = yr and set(value) = yr <- value

let car = new Vehicle()

car.TagNumber <- "792-804"
car.Make <- "Toyota"
car.Model <- "Corlla"
car.Year <- 2002

Instead of initializing the properties outside the object, you can initialize them in the constructor used to create the object. In the parentheses, type the name of each property and assign the desired (and appropriate) value to it. Here is an example:

let suv = new Vehicle(TagNumber = "852M618", Make = "Acura", Model = "MKL", Year = 2010)

There are rules you must follow. One rule is that if you are creating the class, you can decide to provide only the default constructor to it. Here is an example:

type Vehicle() =
    let mutable tag = ""
    let mutable mk = ""
    let mutable mdl = ""
    let mutable yr = 0
    
    member this.TagNumber with get() = tag and set(value) = tag <- value
    member this.Make with get() = mk and set(value) = mk <- value
    member this.Model with get() = mdl and set(value) = mdl <- value
    member this.Year with get() = yr and set(value) = yr <- value

let suv = new Vehicle(TagNumber = "852M618", Make = "Acura", Model = "MKL", Year = 2010)

In the same way, you can apply this feature to the classes of Windows controls we have used so far. Here is an example:

open System
open System.Windows.Forms

type CustomerInvoice(consumption, txRate, discRate) =
    let mutable tx = txRate
    let mutable disc = if discRate > 0.00 then discRate else 0.00

    member this.TotalCharges
        with get() =
            if consumption <= 1.3779 then
                consumption * 45.50
            elif consumption <= 0.9607 then
                consumption * 84.65
            elif consumption <= 0.5258 then
                consumption * 112.85
            else
                consumption * 122.74
            
    member this.DiscountAmount with get() = this.TotalCharges * disc / 100.00
    member this.TaxAmount
        with get() =
            let priceAfterDiscountRate = this.TotalCharges - this.DiscountAmount
            priceAfterDiscountRate * tx / 100.00
    member this.TotalInvoice with get() = this.TotalCharges + this.TaxAmount

// Form: Gas Utility Company
let gasUtilityCompany = new Form(Width = 208, Height = 280, Text = "Gas Utility Company")

// Label: CCF Consumption
let lblConsumption = new Label(Left = 18, Top = 21, Width = 80, Text = "Consumption:")
gasUtilityCompany.Controls.Add lblConsumption

// Text Box: Days Used
let txtConsumption = new TextBox(Left = 115, Top =  18, Width = 46, Text = "0.00")
gasUtilityCompany.Controls.Add txtConsumption

// Label: Discount Rate
let lblDiscountRate = new Label(Left = 18, Top = 48, Width = 85, Text = "Discount Rate:")
gasUtilityCompany.Controls.Add lblDiscountRate

// Text Box: Daily Rate Applied
let txtDiscountRate = new TextBox(Left = 115, Top = 45, Width = 45, Text = "0.00")
gasUtilityCompany.Controls.Add txtDiscountRate

// Label: Discount %
let lblDiscountPercent = new Label(Left = 160, Top = 49, Width = 85, Text = "%")
gasUtilityCompany.Controls.Add lblDiscountPercent

// Label: Tax Rate
let lblTaxRate = new Label(Left = 18, Top = 78, Width = 85, Text = "Tax Rate:")
gasUtilityCompany.Controls.Add lblTaxRate

// Text Box: Tax Rate
let txtTaxRate = new TextBox(Left = 115, Top = 74, Width = 45, Text = "6.75")
gasUtilityCompany.Controls.Add txtTaxRate

// Label: Tax %
let lblTaxPercent = new Label(Left = 160, Top = 78, Width = 85, Text = "%")
gasUtilityCompany.Controls.Add lblTaxPercent

// Label: Sub-Total
let lblTotalCharges = new Label(Left = 18, Top = 134, Width = 80, Text = "Total Charges:")
gasUtilityCompany.Controls.Add lblTotalCharges

// Text Box: Sub-Total
let txtTotalCharges = new TextBox(Left = 115, Top = 132, Width = 70, Text = "0.00")
gasUtilityCompany.Controls.Add txtTotalCharges

// Label: Discount Amount
let lblDiscountAmount = new Label(Left = 18, Top = 160, Width = 95, Text = "Discount Amount:")
gasUtilityCompany.Controls.Add lblDiscountAmount

// Text Box: Discount  Amount
let txtDiscountAmount = new TextBox(Left = 115, Top = 158, Width = 70, Text = "0.00")
gasUtilityCompany.Controls.Add txtDiscountAmount

// Label: Tax Amount
let lblTaxAmount = new Label(Left = 18, Top = 184, Width = 80, Text = "Tax Amount:")
gasUtilityCompany.Controls.Add lblTaxAmount

// Text Box:Tax  Amount
let txtTaxAmount = new TextBox(Left = 115, Top = 184, Width = 70, Text = "0.00")
gasUtilityCompany.Controls.Add txtTaxAmount

// Label: Amount to Pay
let lblAmountDue = new Label(Left = 18, Top = 212, Width = 80, Text = "Amount Due:")
gasUtilityCompany.Controls.Add lblAmountDue

// Text Box: Amount to Pay
let txtAmountDue = new TextBox(Left = 115, Top = 210, Width = 70, Text = "0.00")
gasUtilityCompany.Controls.Add txtAmountDue

// Button: Calculate
let btnCalculate = new Button(Left = 115, Top = 102, Width = 70, Text = "Calculate")

let btnCalculateClick e =
    let consumption = float txtConsumption.Text
    let taxRate = float txtTaxRate.Text
    let discRate = float txtDiscountRate.Text

    let bill = new CustomerInvoice(consumption, taxRate, discRate)

    let strTotalCharges = sprintf "%0.02f" bill.TotalCharges
    let strTaxAmount = sprintf "%0.02f" bill.TaxAmount
    let strDiscountAmount = sprintf "%0.02f" bill.DiscountAmount
    let strTotal = sprintf "%0.02f" bill.TotalInvoice
    
    txtTotalCharges.Text  <- strTotalCharges
    txtTaxAmount.Text <- strTaxAmount
    txtDiscountAmount.Text <- strDiscountAmount
    txtAmountDue.Text  <- strTotal

btnCalculate.Click.Add btnCalculateClick
gasUtilityCompany.Controls.Add btnCalculate

do Application.Run gasUtilityCompany

Remember that the main reason you declare a variable is because you intend to use the same value more than once. If you will use a value only once, there is no need to declare a variable for it. A typical example is the labels on a form. The user cannot change their values and if you need a label only when you are creating it, there is no need to declare a variable for it. In this case, you can just pass the creation of the label to the Controls.Add() method of its container class. Here are examples:

open System
open System.Windows.Forms

type CustomerInvoice(consumption, txRate, discRate) =
    let mutable tx = txRate
    let mutable disc = if discRate > 0.00 then discRate else 0.00

    member this.TotalCharges
        with get() =
            if consumption <= 1.3779 then
                consumption * 45.50
            elif consumption <= 0.9607 then
                consumption * 84.65
            elif consumption <= 0.5258 then
                consumption * 112.85
            else
                consumption * 122.74
            
    member this.DiscountAmount with get() = this.TotalCharges * disc / 100.00
    member this.TaxAmount
        with get() =
            let priceAfterDiscountRate = this.TotalCharges - this.DiscountAmount
            priceAfterDiscountRate * tx / 100.00
    member this.TotalInvoice with get() = this.TotalCharges + this.TaxAmount

// Form: Gas Utility Company
let gasUtilityCompany = new Form(Width = 208, Height = 280, Text = "Gas Utility Company")

// Label: CCF Consumption
gasUtilityCompany.Controls.Add(new Label(Left = 18, Top = 21, Width = 80, Text = "Consumption:"))

// Text Box: Days Used
let txtConsumption = new TextBox(Left = 115, Top =  18, Width = 46, Text = "0.00")
gasUtilityCompany.Controls.Add txtConsumption

// Label: Discount Rate
gasUtilityCompany.Controls.Add(new Label(Left = 18, Top = 48, Width = 85, Text = "Discount Rate:"))

// Text Box: Daily Rate Applied
let txtDiscountRate = new TextBox(Left = 115, Top = 45, Width = 45, Text = "0.00")
gasUtilityCompany.Controls.Add txtDiscountRate

// Label: Discount %
gasUtilityCompany.Controls.Add(new Label(Left = 160, Top = 49, Width = 85, Text = "%"))

// Label: Tax Rate
gasUtilityCompany.Controls.Add(new Label(Left = 18, Top = 78, Width = 85, Text = "Tax Rate:"))

// Text Box: Tax Rate
let txtTaxRate = new TextBox(Left = 115, Top = 74, Width = 45, Text = "6.75")
gasUtilityCompany.Controls.Add txtTaxRate

// Label: Tax %
gasUtilityCompany.Controls.Add(new Label(Left = 160, Top = 78, Width = 85, Text = "%"))

// Label: Sub-Total
gasUtilityCompany.Controls.Add(new Label(Left = 18, Top = 134, Width = 80, Text = "Total Charges:"))

// Text Box: Sub-Total
let txtTotalCharges = new TextBox(Left = 115, Top = 132, Width = 70, Text = "0.00")
gasUtilityCompany.Controls.Add txtTotalCharges

// Label: Discount Amount
gasUtilityCompany.Controls.Add(new Label(Left = 18, Top = 160, Width = 95, Text = "Discount Amount:"))

// Text Box: Discount  Amount
let txtDiscountAmount = new TextBox(Left = 115, Top = 158, Width = 70, Text = "0.00")
gasUtilityCompany.Controls.Add txtDiscountAmount

// Label: Tax Amount
gasUtilityCompany.Controls.Add(new Label(Left = 18, Top = 184, Width = 80, Text = "Tax Amount:"))

// Text Box:Tax  Amount
let txtTaxAmount = new TextBox(Left = 115, Top = 184, Width = 70, Text = "0.00")
gasUtilityCompany.Controls.Add txtTaxAmount

// Label: Amount to Pay
gasUtilityCompany.Controls.Add(new Label(Left = 18, Top = 212, Width = 80, Text = "Amount Due:"))

// Text Box: Amount to Pay
let txtAmountDue = new TextBox(Left = 115, Top = 210, Width = 70, Text = "0.00")
gasUtilityCompany.Controls.Add txtAmountDue

// Button: Calculate
let btnCalculate = new Button(Left = 115, Top = 102, Width = 70, Text = "Calculate")

let btnCalculateClick e =
    let consumption = float txtConsumption.Text
    let taxRate = float txtTaxRate.Text
    let discRate = float txtDiscountRate.Text

    let bill = new CustomerInvoice(consumption, taxRate, discRate)

    let strTotalCharges = sprintf "%0.02f" bill.TotalCharges
    let strTaxAmount = sprintf "%0.02f" bill.TaxAmount
    let strDiscountAmount = sprintf "%0.02f" bill.DiscountAmount
    let strTotal = sprintf "%0.02f" bill.TotalInvoice
    
    txtTotalCharges.Text  <- strTotalCharges
    txtTaxAmount.Text <- strTaxAmount
    txtDiscountAmount.Text <- strDiscountAmount
    txtAmountDue.Text  <- strTotal

btnCalculate.Click.Add btnCalculateClick
gasUtilityCompany.Controls.Add btnCalculate

do Application.Run gasUtilityCompany

Another rule is that, if the name of your class is used as a contructor that takes at least one parameter, you should (must) have a constructor that takes no argument. Here is an example:

type Vehicle(tagNbr, make, model, year) =
    let mutable tag = ""
    let mutable mk = ""
    let mutable mdl = ""
    let mutable yr = 0
    
    member this.TagNumber with get() = tag and set(value) = tag <- value
    member this.Make with get() = mk and set(value) = mk <- value
    member this.Model with get() = mdl and set(value) = mdl <- value
    member this.Year with get() = yr and set(value) = yr <- value
    new() = Vehicle("", "", "", 1990)

let suv = new Vehicle(TagNumber = "852M618", Make = "Acura", Model = "MKL", Year = 2010)

Properties and Reference Cells

Instead of an explicit mutable member, you can use a reference cell to control the value of a property outside the class. In the class, start by assigning a ref value to the member variable. You can use a value of your choice or an argument from the constructor of the class. Here is an example:

type EmployeePayroll(hSalary) =
    let sal = ref hSalary

When implementing the property, to assign a value to the get() function, get a reference to the member variable using the ! operator. To assign the argument of the set() function, use the := operator. Here is an example:

type EmployeePayroll(hSalary) =
    let sal = ref hSalary

    member this.HourlySalary with get() = !sal and set(value) = sal := value

To specify or set the value of the property outside the class, use the <- operator. Here are examples:

open System
open System.Windows.Forms

type EmployeePayroll(hSalary, tWorked) =
    let sal = ref hSalary
    let tme = ref tWorked
    let overtimeSalary = !sal * 1.50

    member this.HourlySalary with get() = !sal and set(value) = sal := value
    member this.TimeWorked   with get() = !tme and set(value) = tme := value
    member this.RegularTime  with get() = if !tme <= 40.00 then !tme else 40.00
    member this.Overtime     with get() = if !tme <= 40.00 then 0.00 else !tme - 40.00
    member this.RegularPay   with get() = if !tme <= 40.00 then !sal * !tme else !sal * 40.00
    member this.OvertimePay  with get() = if !tme <= 40.00 then 0.00 else (!tme - 40.00) * !sal * 1.50
    member this.GrossSalary  with get() = this.RegularPay + this.OvertimePay

// Form: Payroll Preparation
let payrollPreparation = new Form(Width = 300, Height = 240, Text = "Payroll Preparation")

// Label: Hourly Salary
let lblHourlySalary = new Label(Left = 18, Top = 22, Width = 80, Text = "Hourly Salary:")
payrollPreparation.Controls.Add lblHourlySalary

// Text Box: Hourly Salary
let txtHourlySalary = new TextBox(Left = 115, Top =  18, Width = 75, Text = "0.00")
payrollPreparation.Controls.Add txtHourlySalary

// Label: Time Worked
let lblTimeWorked = new Label(Left = 18, Top = 48, Width = 85, Text = "Time Worked:")
payrollPreparation.Controls.Add lblTimeWorked

// Text Box: Time Worked
let txtTimeWorked = new TextBox(Left = 115, Top = 45, Width = 75, Text = "0.00")
payrollPreparation.Controls.Add txtTimeWorked

// Button: Prepare
let btnPrepare = new Button(Left = 195, Top = 43,Text = "Prepare")
payrollPreparation.Controls.Add btnPrepare

// Label: Line 1
let lblLine1 = new Label(Left = 11, Top = 68, Width = 385, Height = 13)
lblLine1.Text <- "-----------------------------------------------------------------------"
payrollPreparation.Controls.Add lblLine1

// Label: Time
let lblTime = new Label(Left = 107, Top = 84, Width = 30, Text = "Time")
payrollPreparation.Controls.Add lblTime

// Label: Pay
let lblPay = new Label(Left = 173, Top = 84, Width = 30, Text = "Pay")
payrollPreparation.Controls.Add lblPay

// Label: Regular
let lblRegular = new Label(Left = 18, Top = 113, Width = 85, Text = "Regular:")
payrollPreparation.Controls.Add lblRegular

// Text Box: Regular Time
let txtRegularTime = new TextBox(Left = 110, Top = 110, Width = 50, Text = "0.00")
payrollPreparation.Controls.Add txtRegularTime

// Text Box: Regular Pay
let txtRegularPay = new TextBox(Left = 167, Top = 110, Width = 50, Text = "0.00")
payrollPreparation.Controls.Add txtRegularPay

// Label: Overtime
let lblOvertime = new Label(Left = 18, Top = 134, Width = 80, Text = "Overtime:")
payrollPreparation.Controls.Add lblOvertime

// Text Box: Overtime
let txtOvertimeTime = new TextBox(Left = 110, Top = 136, Width = 50, Text = "0.00")
payrollPreparation.Controls.Add txtOvertimeTime

// Text Box: Overtime Pay
let txtOvertimePay = new TextBox(Left = 167, Top = 136, Width = 50, Text = "0.00")
payrollPreparation.Controls.Add txtOvertimePay

// Label: Line 2
let lblLine2 = new Label(Left = 11, Top = 157, Width = 385, Height = 13)
lblLine2.Text <- "-----------------------------------------------------------------------"
payrollPreparation.Controls.Add lblLine2

// Label: Gross Salary
let lblGrossSalary = new Label(Left = 18, Top = 178, Width = 85, Text = "Gross Salary:")
payrollPreparation.Controls.Add lblGrossSalary

// Text Box: Gross Salary
let txtGrossSalary = new TextBox(Left = 110, Top = 175, Width = 70, Text = "0.00")
payrollPreparation.Controls.Add txtGrossSalary

let btnPrepareClick e =
    let hourlySalary = float txtHourlySalary.Text
    let timeWorked = float txtTimeWorked.Text

    let payroll = new EmployeePayroll(hourlySalary, timeWorked)

    let strRegularTime = sprintf "%0.02f" payroll.RegularTime
    let strOvertime    = sprintf "%0.02f" payroll.Overtime
    let strRegularPay  = sprintf "%0.02f" payroll.RegularPay
    let strOvertimePay = sprintf "%0.02f" payroll.OvertimePay
    let strGrossSalary = sprintf "%0.02f" payroll.GrossSalary
    
    txtRegularTime.Text <- strRegularTime
    txtRegularPay.Text  <- strRegularPay
    txtOvertimeTime.Text <- strOvertime
    txtOvertimePay.Text <- strOvertimePay
    txtGrossSalary.Text  <- strGrossSalary

btnPrepare.Click.Add btnPrepareClick

// Button: Close
let btnClose = new Button(Left = 195, Top = 173, Text = "Close")
let btnCloseClick e = payrollPreparation.Close()
btnClose.Click.Add btnCloseClick
payrollPreparation.Controls.Add btnClose

do Application.Run payrollPreparation

Here is an example of running the program:

Properties and Reference Cells Properties and Reference Cells
   
 

Home Copyright © 2012-2015, FunctionX Home