Fundamentals of Classes

Introduction

A class is a list of characteristics used to describe an object. In the list, each item describes a particular aspect. For example, a class used to describe a car for the motor vehicle office would have such items as the tag number, the make, the model, the year, etc.

To create a class, start with the following formula:

type class-name() =

Start with the type keyword.

The Name of a Class

After the type keyword, add a name for the class. The name of a class primarily follows the rules of names we saw for a variable. Other than that:

After the name of the class, add some (empty) parentheses and the assignment operator, =. Here is an example:

type State() =

Introduction to the Local Variables of a Class

A class must have at least one piece of information. To create such a piece of information, you can at least declare a variable after the = sign of the class. Here is an example:

type State() = let name = "North Carolina"

A typical class is not as simple as that. This means that a typical has more than one piece of information. In that case, you cannot declare more than one variable on the same line on which the class is created. In that case, after the = sign, declare the variable(s) on the next line after the = sign. In that case also, the pieces of information must be indented. Here is an example:

type State() =
    let name = "North Carolina"

In the same way, you can declare other variables in the class. Here are examples:

type Vehicle() =
    let tag = "485E702"
    let make = "Toyota"
    let model = "Corolla"
    let year = 2012

The Body of a Class

The body of a class is the section that contains its pieces of information. If the class contains only one variable and that variable is declared on the same line as the class, the body is from after the = sign to the end of the line. If the variables are declared on the line after the = sign, the body of the class is from the line below = to the last indented line.

Creating an Object

As mentioned in our introduction, a class provides only the description of an object. To actually create an object, you must declare a variable of a class. To do this, type the let operator followed by a name for the object. The name follows the rules we saw for names of variables. To complete the creation of the object, assign the parenthesized name of the class. Here is an example:

type State() =
    let name = "North Carolina"

let nc = State()

Since that declaration is a statement, you can end it with a(n optioanl) semicolon if you want.

A New Object

When creating an oject, if you want, you can apply an operator named new to the cass object. To do this, type new between the = sign of the variable and the parenthesized name of the class. Here is an example:

type State() =
    let name = "North Carolina"

let nc = new State()

Accessing the Members of a Class

One of the advantages that members of a class enjoy is that they can access each other easily, by simply using their names. For example, a method of a class can access a variable declared in the same class by simply using the name of that variable. Here are examples:

type Vehicle() =
    let tag = "4857"
    let make = "Toyota"
    let model = "Corolla"
    let year = 2012

    printfn "Vehicle Information"
    printfn "--------------------"
    printfn "Tag #: %s" tag
    printfn "Make:  %s" make
    printfn "Model: %s" model
    printfn "Year:  %d" year

Doing the Binding in a Class

You may remember that the F# language provides the do keyword that allows you to perform the job of a function without explicitly creating a function. In the body of a class, you can use that keyword to perform one or more actions where you would need a function. For example, in a class, you can create a do section to perform an action, such as displaying some values. This can be done as follows:

type Vehicle() =
    let tag = "4857"
    let make = "Toyota"
    let model = "Corolla"
    let year = 2012

    do
        printfn "Vehicle Information"
        printfn "--------------------"
        printfn "Tag #: %s" tag
        printfn "Make:  %s" make
        printfn "Model: %s" model
        printfn "Year:  %d" year

Remember that, at any time, to use an object, you can declare a variable of the class. Here is an example:

type Vehicle() =
    let tag = "4857"
    let make = "Toyota"
    let model = "Corolla"
    let year = 2012

    do
        printfn "Vehicle Information"
        printfn "--------------------"
        printfn "Tag #: %s" tag
        printfn "Make:  %s" make
        printfn "Model: %s" model
        printfn "Year:  %d" year

let car = Vehicle() 
printfn "==================="

If you had created a do section in the class as done above, that action would execute. As a result, the above code would produce:

Vehicle Information
--------------------
Tag #: 4857
Make:  Toyota
Model: Corolla
Year:  2012
===================

Press any key to close this window . . .

Early Topics on Creating a Class

Delimiting the Body of a Class

We already saw that the body of a class is the section after its = symbol. To let you explicitly indicate the body of a class, the F# language provides the class and the end keywords. To indicate the body of a class, after the = sign of the class, type class, an empty space, and type end. iables and indent them. Here is an example:

type Vehicle() = class end

The section between class and end is the body of the class. Between those two keywords, you can write the code you want. Here is an example:

type State() = class let age = 36 end

As you can see, if you need to have many code items in your class, it is not realist to write all that code on one line. The alternative is to write class and end each on its own line. Then, on the lines between class and end, write your code.

An Empty Class

Sometimes you just want to test a concept about classes but you don't need any formal functionality. In this case, you simply want an empty class. In that case, create a class with just the class and the end keywords for its body, as we saw above:

type Something() = class end

Remember that you can class on the next line after the = symbol, and write end thereafter. This can be done as follows:

type Something() =
    class
    end

Introduction to the Fields of a Class

As seen already, you can declare one or more variables in the body of a class. Such a variable is referred to as a field. Here is an example:

type Vehicle() =
    let tag = "485E702"
    let make = "Toyota"
    let model = "Corolla"
    let year = 2012

Remember that, if you want, you can delimit the body of a class with a class and and end lines. Between those lines, you can populate the class any way you want, such as creating fields. This can be done as follows:

type Vehicle() =
    class
        let tag = "485E702"
        let make = "Toyota"
        let model = "Corolla"
        let year = 2012
    end

Introduction to Class Construction

Overview

In our introduction, we mentioned that a class is a technique to lay a foundation for, or to describe, an object. To start such a foundation, you must use a special type of function that holds the primary characteristics of the object. That special function is called a constructor.

A constructor of a class is a special function with some characteristics. A constructor:

A Primary Constructor

In our introduction to classes, we mentioned that, when you are creating a class, you can add parentheses to the name of the class. Here is an example:

type Property() =
    let price = 397_495

Remember that, when necessary, you can declare a variable of the class and use the object. Here is an example:

type Property() =
    let price = 397_495

let sf = Property()

The primary way to create a constructor is to add parentheses to the name of the class, as done above. The combination of the name of the class and those parentheses is a constructor, and that constructor is referred to as the primary constructor of the class.

The Parameters of a Constructor

Adding a Parameter to a Constructor

A constructor is primarily a function. As such, it can receive arguments. To indicate that a constructor must be passed an argument, in the parentheses of the primary constructor, type the desired name of the parameter. Here is an example:

type Property(something) =
    . . .

If you do this, when you create an object of the class, you must pass an argument to the class name that is assigned to the object. Here is an example:

type Vehicle(something) =
    let tag = "4857"
    let make = "Toyota"
    let model = "Corolla"
    let year = 2012

    do 
        printfn "Vehicle Information"
        printfn "--------------------"
        printfn "Tag #: %s" tag
        printfn "Make:  %s" make
        printfn "Model: %s" model
        printfn "Year:  %d" year

let car = Vehicle(0)

Notice that, in the above example, neither the class nor the compiler knows the type of value that the parameter holds. As a result, in the above declaration, you can pass the argument with any value. Luckily, we didn't use that argument. As a result, the above code would produce the same result as the previous program.

A Default Constructor

As mentioned in our introduction, a constructor is a function that is created with the name of the class but the function doesn't start with the let keyword. Since you are creating a function, you must add parentheses to its name. We can start as follows:

type Property(something) =
    Property()

Remember that, later on, to use a class, you will create an object of that class. That action (creating an object of a class) instructs the compiler to allocate memory for the object. In many computer languages (C++, Java, C#, Visual Basic, etc), this is done by using an operator named new (New for Visual Basic). F# also supports this operator. In a class, that keyword is defined like a function. That is, first type new. Since this operator is defined like a function, you must add parentheses to its name. To finalize the constructor, you must assign a constructor, such as the the above constructor, to it. As a result, here is an example of creating a (default) constructor:

type Property(something) =
    new() = Property()

You can then add the desired field(s) to the class, as we did earlier. Here is an example:

type Property(something) =
    let number = 927_571_395
    let beds   = 3
    let baths  = 2.50f
    let price  = 397_495

    do 
        printfn "Property Information"
        printfn "---------------------------"
        printfn "Property #:    %i" number
        printfn "Bedrooms:      %d" beds
        printfn "Bathrooms:     %0.2f" baths
        printfn "Market Value:  %d" price

    new() = Property()

Remember that, in the above class, the primary constructor takes a parameter. Therefore, when creating an object of that class, you must pass an argument to the constructor. Here is an example:

type Property(something) =
    let number = 927_571_395
    let beds   = 3
    let baths  = 2.50f
    let price  = 397_495

    do 
        printfn "Property Information"
        printfn "---------------------------"
        printfn "Property #:    %i" number
        printfn "Bedrooms:      %d" beds
        printfn "Bathrooms:     %0.2f" baths
        printfn "Market Value:  %d" price

    new() = Property()

let house = Property(0)
printfn "=========================="

This would produce:

Property Information
---------------------------
Property #:    927571395
Bedrooms:      3
Bathrooms:     2.50
Market Value:  397495
==========================

Press any key to close this window . . .

A Data Type for a Parameter

If you provide a parameter to a constructor, and provide that parameter by name, the compiler would first consider that that parameter is an integer. In most case, you will want to indicate the type of the parameter. As seen in previous sections, to indicate the name of parameter, after its name, type a colon followed by the desired type. Here is an example:

type Property(number : string) =
    new() = Property()

Passing Zero Arguments to a Constructor

A constructor can use more than one parameter. As always, to provide those parameters, give the name of each in the parentheses of the constructor. The names are separated by comas. There are rules you must follow for the creation of constructor:

If the name of the class has empty parentheses, this means that the class has a primary constructor. In that case, you cannot create an additional default constructor. This means that you cannot add a new() member function that has empty parentheses. As a result, the following code will produce an error:

type State() =
    let display _ = printfn "States Statistics"

    new() = State()

let nc = new State()

nc.display()

Obviously the solution here is to add a parameter to the primary constructor. The second rule is that, when declaring a variable of the class, you must pass an argument to the name of the class. In the body of the class, you can ignore or use the argument anyway you want. Here is an example:

type State(code : string) =
    do 
        if code = "US-MO" then
            printfn "State Statistics"
            printfn "-------------------------------------"
            printfn "Country:    United States of America"
            printfn "State Name: Missouri"
            printfn "Capital:    Jefferson City"
            printfn "Area:       180,560 km2"
        elif code = "DE-BY" then
            printfn "State Statistics"
            printfn "-------------------------------------"
            printfn "Country:    Germany"
            printfn "State Name: Bavaria"
            printfn "Capital:    Munich"
            printfn "Area:       70_550.19 km2"
        else
            printfn "Unknow State"

    new() = State() 

let institution = State("DE-BY")

This would produce:

State Statistics
-------------------------------------
Country:    Germany
State Name: Bavaria
Capital:    Munich
Area:       70_550.19 km2

Press any key to close this window . . .

Here is another way to call the constructor:

type State(code : string) =
    do 
        if code = "US-MO" then
            printfn "State Statistics"
            printfn "-------------------------------------"
            printfn "Country:    United States of America"
            printfn "State Name: Missouri"
            printfn "Capital:    Jefferson City"
            printfn "Area:       180,560 km2"
        elif code = "DE-BY" then
            printfn "State Statistics"
            printfn "-------------------------------------"
            printfn "Country:    Germany"
            printfn "State Name: Bavaria"
            printfn "Capital:    Munich"
            printfn "Area:       70_550.19 km2"
        else
            printfn "Unknow State"

    new() = State() 

let institution = State("US-MO")

This would produce:

State Statistics
-------------------------------------
Country:    United States of America
State Name: Missouri
Capital:    Jefferson City
Area:       180,560 km2

Press any key to close this window . . .

Adding Parameters to a New() Constructor

You can add as many new() constructors as you want as long as each has a different number of parameters (than the others), including the one created using the name of the class. To add a new constructor using the new() member function, pass the desired number of parameters to both that new() member function and the class name assigned to it. In the parentheses of that class name, you must pass the number of parameters greater than or equal to the number of parameters of the new() constructor.

If the assigned class name has the same number of parameters as the new() constructor, the compiler will assign the parameters based on their positions. Here is an example:

type Property(number : int, price : int) =
    . . .

    new() = Property()
    new(some, thing) = Property(some, thing)

Once again, in the body of the class, you can ignore or use the parameter(s). Here is an example:

type Property(number : int, price : int) =
    do 
        printfn "Property Information"
        printfn "---------------------------"
        printfn "Property #:    %i" number
        printfn "Market Value:  $%d" price

    new(nbr, value) = Property(nbr, value)

let house = Property(927_571_395, 397_495)
printfn "=========================="

If the assigned class name has more parameters than the new() constructor, you must specify how the extra parameter(s) will get its (their) value(s). One way to take care of this is to assign a default value to each extra parameter (of course, there are other ways we will see). Here is an example:

type Vehicle(tag : string, make : string, model : string) =

. . .

new(tag : string, make : string, model : string, yr : int) = Vehicle(tag, make, model, yr = 1900)

Creating Different Constructors

Remember that you can add as many parameters as you want to the primary constructor of a class. In the body of the class, you can ignore or use those parameters. Here is an example:

type Property(number : int, price : int, beds : int, baths : float, basement) =
    do 
        printfn "Property Information"
        printfn "--------------------------------"
        printfn "Property #:         %i" number
        printfn "Bedrooms:           %i" beds
        printfn "Bathrooms:          %0.1f" baths
        printfn "Market Value:       %d" price

        if basement = true then
            printfn "Finished Basement:  Yes"
        else
            printfn "Finished Basement:  No"


let house = Property(173_046_995, 688_250, 5, 3.5, true)
printfn "==============================="

This would produce:

Property Information
--------------------------------
Property #:         173046995
Bedrooms:           5
Bathrooms:          3.5
Market Value:       688250
Finished Basement:  Yes
===============================

Press any key to close this window . . .

In a class, you can create many constructors. Each constructor must use a different number of parameters. Both the new() member function and the constructor that is assigned to it must use the same parameters. Here are examples:

type Property(...) =

    ...
    
    new() = Property()
    new(code, value) = Property(code, value)
    new(code, value, under) = Property(code, value, under)
    new(code, value, sleep, showers, under) = Property(code, value, sleep, showers, under)

When creating the primary constructor of the class, you must specify a number of parameters that corresponds to one of the new() constructors you had created in the class. For example, the aboe class has four constructors that use 0, 2, 3, or 5 parameters. Therefore, your primary constructor can use 2, 3, and 5 parameters. In the body of the class, you can use or ignore some parameters, or you can ignore or use all paramenters. Here is an example of the above class with a primary constructor that uses only three parameters:

type Property(number : int, price : int, beds : int) =
    do 
        printfn "Property Information"
        printfn "--------------------------------"
        printfn "Property #:         %i" number
        printfn "Bedrooms:           %i" beds
        printfn "Market Value:       %d" price

    new() = Property()
    new(code, value) = Property(code, value)
    new(code, value, under) = Property(code, value, under)
    new(code, value, sleep, showers, under) = Property(code, value, sleep, showers, under)

let house = Property(682_957_680, 493_850, 4)
printfn "==============================="

This would produce:

Property Information
--------------------------------
Property #:         682957680
Bedrooms:           4
Market Value:       493850
===============================

Press any key to close this window . . .

Here is an example of the same class with a primary constructor that uses five parameters:

type Property(number : int, price : int, beds : int, baths : float, basement) =
    do 
        printfn "Property Information"
        printfn "--------------------------------"
        printfn "Property #:         %i" number
        printfn "Bedrooms:           %i" beds
        printfn "Bathrooms:          %0.1f" baths
        printfn "Market Value:       %d" price

        if basement = true then
            printfn "Finished Basement:  Yes"
        else
            printfn "Finished Basement:  No"

    new() = Property()
    new(code, value) = Property(code, value)
    new(code, value, under) = Property(code, value, under)
    new(code, value, sleep, showers, under) = Property(code, value, sleep, showers, under)

let house = Property(173_046_995, 688_250, 5, 3.5, true)
printfn "==============================="

This would produce:

Property Information
--------------------------------
Property #:         173046995
Bedrooms:           5
Bathrooms:          3.5
Market Value:       688250
Finished Basement:  Yes
===============================

Press any key to close this window . . .

Adding Uninitialized Members

Introduction

In your class, if you create a field using the let keyword, you must initiliaze that variable. The F# language allows you to add a member variable that is not initialized. This is done using the val keyword. To start, in the class, use the val (instead of the let) keyword to declare a variable and apply the desired data type to the variable. Here is an example:

type Customer =
    val FullName : string;

A val field brings a set of requirements to a class. Normally, to use a class, you must declare a variable from it, which is naturally done using the let keyword; but you must have a constructor for the class. You have many options.

Initializing Uninitialized Members

Remember that, if your class doesn't have a primary constructor, you can create one or more constructors using the new keyword. If you create a constructor that doesn't take a parameter, you must initialize the val field with a default value, based on its type. For a natural number member, this would be 0. For a decimal number, this would be 0.00. For a Boolean variable, this would be true or false. For a character or a string, this would be an empty string. Here is an example:

type Customer =
    val FullName : string;

    new() = { FullName = "" }

If you want to initialize the val variable, you must add a constructor that takes a parameter that corresponds to the val field. When initializing the val member variable, assign the parameter to it. Here is an example:

type Customer =
    val FullName : string;

    new() = { FullName = "" }
    new(name) = { FullName = name }

After doing this, you can access the val member outside the class after creating an object that using the constructor that takes a value for the val parameter. Here is an example:

type Customer =
    val FullName : string;
    new() = { FullName = "" }
    new(name) = { FullName = name }

let client = new Customer("Jessica Constance Bernstein");

printfn "Customer Name: %s\n" client.FullName;

This would produce:

Customer Name: Jessica Constance Bernstein

Press any key to continue . . .

In the same way, you can add as many val fields as you want. Remember to create a constructor that can be used to initialize the member(s) so it (they) can be accessed outside the class. Here are examples:

type Customer =
    val FullName : string;
    val PhoneNumber : string
    val EmailAddress : string

    new() = { FullName = ""; PhoneNumber = ""; EmailAddress = "" }
    new(name) = { FullName = name; PhoneNumber = ""; EmailAddress = "" }
    new(name, phone) = { FullName = name; PhoneNumber = phone; EmailAddress = "" }
    new(name, phone, email) = { FullName = name; PhoneNumber = phone; EmailAddress = email }

let client = new Customer("Jessica Constance Bernstein", "103-118-9274", "jcbern@email.net");

printfn "Customer Name: %s" client.FullName;
printfn "Phone Number:  %s" client.PhoneNumber;
printfn "Email Address: %s\n" client.EmailAddress;

Initializing With Default Values

An alternative to initialize a val field 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;

After doing this, you can initialize the field with the desired value. If you are working outside the class, to start, declare a variable of the class, then use that variable to initialize the mutable val field using the < operator. Here is an example:

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

let client = new Customer();

client.FullName <- "Zachary Cox"

printfn "Customer Name: %s\n" client.FullName;

This would produce:

Customer Name: Robert 

Press any key to continue . . .

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


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