Fundamentals of Properties

Introduction

A let 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 a (private) let variable:

type Payroll() =
    let summary = string

On the other hand, member variables can be used inside a class or can communicate with the objects that are outside the class. 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. 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 roles, there are three categories of properties.

Introduction to Read-Only Properties

Getting the Value of the Property

A property is described as read-only if its only job is to present 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 self-identifier | class-name.property-name with get() = some-value

Start with the member keyword and an empty space. This is followed by a self-identifier. The self-identifier is followed by a period.

The self-identifier which can be any readable letter in lowercase (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, v, w, x, y, z) or uppercase. Here are examples

type Vehicle() =
    member a.
    member a.
    member a.
    member a.

The self-identifier can also be any word of your choice. If you decide to use a word, that word must follow the rules we saw for names of variables. If you want to do like Python programmers, you can use the word self as the self-identifier. If you want to do like Visual Basic programmers, you can use the word me, Me, my, or My as the self-identifier. If you want to do like C++/Java/C# programmers, you can use the word this (or This) as the self-identifier. The self-identifier can also be the name of the class. Here are examples:

type Vehicle() =
    member Vehicle.
    member Vehicle.
    member Vehicle.
    member Vehicle.

and the desired name of the property. The name of the property follows the rules we saw for names of classes (start in uppercase; add letters, digits, and/or underscores). The name is followed by the with get() expression.

After the with get() expression, type = followed by 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 you want. To access the value of the property outisde, create an object of the class, apply the period operator to the object followed by the name of the property. Here are examples:

type Payroll(salary, weekTime) =
    member me.WeeklySalary with get() = salary * weekTime

let pay = Payroll(26.85, 38.50)

printfn "Payroll Information"
printfn "=--------------------------="
printfn "Net Pay:    %0.02f\n" pay.WeeklySalary

This would produce:

Payroll Information
=--------------------------=
Net Pay:    1033.73

Press any key to continue . . .

Mutating the Value of a Property

Consider the following program that contains a property:

type Circle(radius) =
    member Circle.Radius with get() = radius
    
let pieceOfPaper = Circle(-25.68)

printfn "Circle Characteristics"
printfn "----------------------"
printfn "Radius: %0.02f\n" pieceOfPaper.Radius

This would produce:

Circle Characteristics
----------------------
Radius: -25.68 

Press any key to continue . . .

Instead of passing a value of the constructor directly to a property, you can create a let binding member 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. This can be done as follows:

type Circle(radius) =
    let r = radius
    member Circle.Radius with get() = r
    
let pieceOfPaper = Circle(-25.68)

printfn "Circle Characteristics"
printfn "----------------------"
printfn "Radius: %0.02f" pieceOfPaper.Radius 

This would produce the same result as previously. Notice that a negative value was passed to the constructor, but a circle cannot have a negative value. To address this issue, you can use a mutable let binding member to accept or reject an undesired value. This can be done as follows:

type Circle(radius) =
    let mutable r = 0.00
    do
        if radius < 0.00 then
            r <- 0.00
        else
            r <- radius
    member Circle.Radius with get() = r

let show (circ : Circle) =
    printfn "Circle Characteristics"
    printfn "----------------------"
    printfn "Radius: %0.02f" circ.Radius
    
let round = Circle(-25.68)
show round
printfn "=================================="
let currency = Circle(17.39)
show currency

This would produce:

Circle Characteristics
----------------------
Radius: 0.00
==================================
Circle Characteristics
----------------------
Radius: 17.39

Press any key to continue . . .

Using a Reference Cell on the Value of a Property

Instead of a mutable member variable, you can use a reference cell to refer to the memory area where the member is located and change the value of the property accordingly. To start, you can assign an argument of the constructor to the member. Here is an example:

type Square(side) =
    let s = ref side
    member Square.Side with get() = s

To access the value of the member in the class, you can precede its name with the ! operator. Here is an example:

type Square(side) =
    let s = ref side
    member Square.Side with get() = s
    member Square.Perimeter with get() = !s * 4.00

To get the value of the property outside the class, precede it with the ! operator. Here is an example:

type Square(side) =
    let s = ref side
    member Square.Side with get() = s
    member Square.Perimeter with get() = !s * 4.00
    member Square.Area with get() = !s * !s

let show (sqr : Square) =
    printfn "Square Characteristics"
    printfn "----------------------"
    printfn "Side:      %0.02f" !sqr.Side
    printfn "Perimeter: %0.04f" sqr.Perimeter
    printfn "Area:      %0.04f" sqr.Area
    
let paper = Square(34.06)
show paper

This would produce:

Square Characteristics
----------------------
Side:      34.06
Perimeter: 136.2400
Area:      1160.0836

Press any key to continue . . .

If necessary, anywhere in your class, you can change the value of the member using the := operator. Here is an example:

type Square(side) =
    let s = ref 0.00
    do
        if side < 0.00 then
            s := 0.00
        else
            s := side

    member Square.Side with get() = s
    member Square.Perimeter with get() = !s * 4.00
    member Square.Area with get() = !s * !s

let show (sqr : Square) =
    printfn "Square Characteristics"
    printfn "----------------------"
    printfn "Side:      %0.02f" !sqr.Side
    printfn "Perimeter: %0.04f" sqr.Perimeter
    printfn "Area:      %0.04f\n" sqr.Area
    
let piece = Square(-34.06)
show piece
printfn "=================================="
let paper = Square(34.06)
show paper

This would produce:

Square Characteristics
----------------------
Side:      0.00
Perimeter: 0.0000
Area:      0.0000
==================================
Square Characteristics
----------------------
Side:      34.06
Perimeter: 136.2400
Area:      1160.0836
Press any key to continue . . .

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 self-identifier | class-name.property-name : = 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)

printfn "Circle Characteristics"
printfn "----------------------"
printfn "Radius:        %0.02f" round.Radius
printfn "Diameter:      %0.02f" round.Diameter
printfn "Circumference: %f" round.Circumference
printfn "Area:          %f\n" round.Area

This would produce:

Kolo
Press any key to continue . . .

Introduction to Write-Only Properties

Setting a Value to a Property

A property is described 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 a function named set. This function takes one argument. The formula to create a write-only property is:

member self-identifier | class-name.property-name with set(parameter) = body

As seen already, start with the member keyword followed by a space and a self-identifier or the name of the class. This is followed by a period and a name for the property as we described for the read-only property. This is followed by the with set() expression. In the parentheses of the set() function, 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 Circle.Radius with set(unit) = radius

Mutating the Value of a Read-Only Property

The primary role of a write-only property 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;

printfn "Circle Characteristics"
printfn "----------------------"
printfn "Diameter: %0.02f\n" round.Diameter

This would produce:

Circle Characteristics
----------------------
Diameter: 96.12

Press any key to continue . . .

Using a Reference Cell with a Read-Only Property

Instead of a mutable variable, you can use a reference cell to access the location of the variable. You can start by declaring the member variable and assigning it a ref value. Here is an example:

type Square() =
    let s = ref 0.00

To apply a value to the write-only property, use the := operator. Here is an example:

type Square() =
    let s = ref 0.00

    member Square.Side with set(value) = s := value

To specify the value of the property outside the class, use the <- operator. Here is an example:

type Square() =
    let s = ref 0.00

    member Square.Side with set(value) = s := value
    member Square.Perimeter with get() = !s * 4.00
    member Square.Area with get() = !s * !s

let show (sqr : Square) =
    printfn "Square Characteristics"
    printfn "----------------------"
    printfn "Perimeter: %0.04f" sqr.Perimeter
    printfn "Area:      %0.04f\n" sqr.Area
    
let paper = Square()
paper.Side <- 34.06
show paper

This would produce:

Square Characteristics
----------------------
Perimeter: 136.2400
Area:      1160.0836

Press any key to continue . . .

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 a class. Such a property is described to as read-write. The formula to create a read-write property is a combination of those we used already:

member self-identifier | class-name.property-name
    with get() : = 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 get() function is the same used in the set(some-value) clause. 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

Then create the property following the above formula. If the whole code of the property can fit in one line, create it on a line. Here is an example:

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

To specify or change the value of the property outside the class, use the <- operator. Here is an example:

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
    
let round = Circle()
round.Radius <- 48.06

printfn "Circle Characteristics"
printfn "----------------------"
printfn "Radius:        %0.02f" round.Radius
printfn "Diameter:      %0.02f" round.Diameter
printfn "Circumference: %f" round.Circumference
printfn "Area:          %f\n" round.Area

This would produce:

Circle Characteristics
----------------------
Radius:        48.06
Diameter:      96.12
Circumference: 301.966747
Area:          7256.260935

Press any key to continue . . .

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:

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() = tagNbr
        and set(value) = tagNbr <- value
    member this.Make
        with get() = manufacturer
        and set(value) = manufacturer <- 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("", "", "", 1960)

let car = Vehicle()
car.TagNumber <- "502-484"
car.Make <- "Dodge"
car.Model <- "Charger"
car.Year <- 2014

printfn "Vehicle Information"
printfn "Tag #: %s" car.TagNumber
printfn "Make:  %s" car.Make
printfn "Model: %s" car.Model
printfn "Year:  %i\n" car.Year

This would produce:

Vehicle Information
Tag #: 502-484
Make:  Dodge
Model: Charger
Year:  2014

Press any key to continue . . .

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

Using a Reference Cell

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 Square(side) =
    let s = ref side;

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. To specify or set the value of the property outside the class, use the <- operator. Here are examples:

type Square(side) =
    let s = ref side

    member Square.Side with get() = !s and set(value) = s := value
    member Square.Perimeter with get() = !s * 4.00
    member Square.Area with get() = !s * !s

let show (sqr : Square) =
    printfn "Square Characteristics"
    printfn "----------------------"
    printfn "Side:      %0.02f" sqr.Side
    printfn "Perimeter: %0.04f" sqr.Perimeter
    printfn "Area:      %0.04f" sqr.Area
    printfn "==========================="
    
let paper = Square(25.75)
show paper
paper.Side <- 48.68
show paper

This would produce:

Square Characteristics
----------------------
Side:      25.75
Perimeter: 103.0000
Area:      663.0625

===========================
Square Characteristics
----------------------
Side:      48.68
Perimeter: 194.7200
Area:      2369.7424
===========================

Press any key to continue . . .

The Type of a Primitive Property

Introduction

Because F# is an inferred language, its compiler works 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.

The Type of a Read-Only Property

If you are creating a ready-only property, the formula to follow is:

member 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

The Type of a Write-Only Property

If you want to specify the data type of a write-only property, the updated formula to follow is:

member 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 self-identifier | class-name.property-name
    with get() : data-type = some-value
    and set(parameter) data-type = body

The Type of a Read/Write Property

You can specify the data type to the getter only, to the setter only, or to both. To specify the data type of the property, after the with get/set() expression, type a colon followed by the desired name of the type. The colon or data type is followed by = and a value. 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) = tagNbr <- value
    member this.Make
        with get() = 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)

Options on Creating Properties

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 self-identifier | class-name applied to the name of the property with the val keyword, assign the default value to the name of the property, and use the with get, set expression in place of defining the separate sections of the property. The formula to follow is:

member val property-name = defaut-value = 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

let small = Vehicle("208048")
printfn "Vehicle Registration - Vehicle Information"
printfn "Tag Number: %s" small.TagNumber

A Simple Read-Only Property

The F# language makes it very easy to create a read-only property, which is a property whose value cannot be changed by the user. Remember that, to create a simple property, use the member, an empty space, a self-identifier, a period, and a name for the property. To create it as a read-only property, you can simply assign a value to this description of the property. Here are examples:

type Address() =
    member self.Recipient = "Melissa Durand"
    member self.StreetNumberAndName = "13828 Anderson Briar Road"
    member self.Suite = "D12"
    member self.City = "Lake City"
    member self.State = "South Carolina"
    member self.ZIPCode = "29560"

Remember that you can include the body of the class between class and end. The above example can be written as follows:

type Address() =
    class
    member self.Recipient = "Melissa Durand"
    member self.StreetNumberAndName = "13828 Anderson Briar Road"
    member self.Suite = "D12"
    member self.City = "Lake City"
    member self.State = "South Carolina"
    member self.ZIPCode = "29560"
    end

Remember that, to use a class, you can declare a variable an initialize it with the name of the class. Here is an example:

type Property() =
    member my.MarketValue = 494_680

let sf = Property()

Then, to access a member of the class, type a period and the desired member. Here are examples:

type Address() =
    member self.Recipient = "Melissa Durand"
    member self.StreetNumberAndName = "13828 Anderson Briar Road"
    member self.Suite = "D12"
    member self.City = "Lake City"
    member self.State = "South Carolina"
    member self.ZIPCode = "29560"

let adrs = Address()

printfn "Personal Address"
printfn "------------------------------------------"
printfn "Recipient #: %s" adrs.Recipient
printfn "Address:     %s %s" adrs.StreetNumberAndName adrs.Suite
printfn "City:        %s" adrs.City
printfn "State:       %s" adrs.State
printfn "ZIP-Code:    %s" adrs.ZIPCode
printfn "=========================================="

This would produce:

Personal Address
------------------------------------------
Recipient #: Melissa Durand
Address:     13828 Anderson Briar Road D12
City:        Lake City
State:       South Carolina
ZIP-Code:    29560
==========================================

Press any key to close this window . . .

External Values for Class Members

We already saw that, one way to provide an external value to a property, or to let a user specify the value of a property, is to create a parameter in a constructor, and then assign that value to a simple property. Here is an example:

type Membership(cat) =
    member me.Category = cat

If you want, you can specify the data type of the parameter, and sometimes this is helpful or necessary. Here is an example:

type Vehicle(tag : string) =
    member this.TagNumber = tag

After doing this, to provide a value to the member variable, when creating an object, pass the desired value to the constructor of the class. After doing this, you can access the parameter of the constructor in the body of the class and use its value. Here is an example:

type Membership(cat) =
    do
        printfn "Membership Details"
        printfn "----------------------"
        printfn "Category: %s" cat

    member me.Category = cat

let mbr = Membership("Executive")

This would produce:

Membership Details
----------------------
Category: Executive
==========================================

Press any key to close this window . . .

In the same way, you can create a constructor with as many parameters as you want. Separate the parameters in the parentheses of the constructor with comas. Here is an example:

type Vehicle(tag, make, model) =
    class
	. . . Members
    end

If you want, or to be more explicit, you can (should) specify the data types of the parameters. Here are examples:

type Vehicle(tag : string, make : string, model : string) =
    class
	. . . Members
    end

When creating an aobject, pass the desired value for each member. Then use those members as you see fit. Here are examples:

type Membership(name, cat, fee) =

    do
        printfn "Membership Details"
        printfn "---------------------------"
        printfn "Member Name: %s" name
        printfn "Category:    %s" cat
        printfn "Annual Fee:  $%i" fee

    // Assignments
    member me.Name     = name
    member me.Category = cat
    member me.Fee      = fee

let mbr = Membership("Sandrine Beul", "Executive", 25_000)
printfn "==========================="

This would produce:

Membership Details
---------------------------
Member Name: Sandrine Beul
Category:    Executive
Annual Fee:  $25000
===========================

Press any key to close this window . . .

Remember that, when adding the parameters to a contructor, you can specify their typss. Here are examples:

type Vehicle(tag : string, make : string, model : string, yr : int) =
    member this.TagNumber = tag
    member this.Make = make
    member this.Model = model
    member this.Year = yr

Remember that you can create different constructors using the new member function with different numbers of parameters. Here are examples:

type Vehicle(tag : string, make : string, model : string, yr : int) =
    member this.TagNumber = tag
    member this.Make = make
    member this.Model = model
    member this.Year = yr
    new() = Vehicle()
    new(tag : string) = Vehicle(tag, "Unknown", "N/A", 1900)
    new(tag : string, yr : int) = Vehicle(tag, "Unknown", "N/A", yr)

Previous Copyright © 2014-2024, FunctionX Monday 14 February 2022 Next