Home

Generics

 

Fundamentals of Generics

 

Introduction to Generic Functions

Consider the following function:

let display value = sprintf "%A" value

This function takes a value and puts it in a string. Here is an example:

open System
open System.Windows.Forms

let display value = sprintf "%A" value

let exercise = new Form(Text = "Values", Width = 180, Height = 75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12)
exercise.Controls.Add txtValue

let a = 25
txtValue.Text <- (display a)

System.Windows.Forms.Application.Run exercise

This would produce:

Introduction to Generic Functions

Here is another example of calling the same function:

let a = "Movie Production"
txtValue.Text <- (display a)

This would produce:

Introduction to Generic Functions

The function doesn't specify what type of value it is dealing with. This means that any value can be passed to the function. In fact, this function becomes aware of the type of its parameter only when the function is called. A generic function is one that involves one or more parameters without specifying the type of value the parameter(s) is(are) using but still carrying its(their) normal operation(s). In other words, at the time the function is created, it specifies its operation(s) or role(s) but doesn't restrict it(them) to one particular type. Only when the function is called is(are) the desired type(s) specified.

Passing Generic Arguments

You may already know that you can specify the data type of a parameter by following its name with a colon and the desired type, all of that in parentheses. Here is an example:

let getValue (value : int) = sprintf "%A" value

You also know that you can omit the type of the parameter. As a result, practically all functions in F# are generic by default. Still, to indicate that the type of a parameter is not specified, instead of adding a colon and a data type to it, use the colon but replace the data type with an apostrophe and a letter or a word. Here is an example:

open System
open System.Windows.Forms

let display (value : 'something) = sprintf "%A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width = 180, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12)
exercise.Controls.Add txtValue

let a = 48.07
txtValue.Text <- (getValue a)

System.Windows.Forms.Application.Run(exercise)

This would produce:

Passing Generic Arguments

Here is another example of calling the function with a different type of value:

open System
open System.Windows.Forms

let display (value : 'something) = sprintf "%A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width = 280, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12, Width = 200)
exercise.Controls.Add txtValue

let a = [| "Male"; "Female"; "Unknown" |]
txtValue.Text <- (display a)

System.Windows.Forms.Application.Run(exercise)

This would produce:

Passing Generic Arguments

By tradition, a letter instead of a word is used for the generic type. It can be any letter, but by tradition, most people use the letter a or the letter T. Here is an example:

let display (value : 'T) = sprintf "%A" value

If the function is receiving more than one parameter, if the parameters are of the same type, you can write each in its own parentheses, its name followed by a colon and the generic letter or word preceded by '. Here are examples:

open System
open System.Windows.Forms

let addition (a : 'T) (b : 'T) =
    sprintf "Numbers: %A %A" a b

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=180, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12, Width = 100)
exercise.Controls.Add txtValue

// Passing two integers
let x = 248
let y = 75
txtValue.Text <- (addition x y)

System.Windows.Forms.Application.Run exercise

This would produce:

Passing Generic Arguments

Of course, if the parameters are of the same type, when the function is called, you must pass the same kind of value to the parameters, otherwise you would receive an error. For example the following code produces an error:

// Passing a decimal and an integer
let x = 248.04
let y = 75
addition x y

If the function is taking more than one parameter and the parameters are of different types, you can write each in its own parentheses, its name followed by a colon and its own generic letter or word preceded by '. Here is an example:

open System
open System.Windows.Forms

let getValues (a : 'U) (b : 'V) = sprintf "%A %A" a b

let exercise = new System.Windows.Forms.Form(Text = "Values", Width = 230, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12, Width = 150)
exercise.Controls.Add txtValue

let status = "Full-Time Employee"
let truth = true
txtValue.Text <- (getValues status truth)

System.Windows.Forms.Application.Run exercise

This would produce:

Passing Generic Arguments

Here is another example:

open System
open System.Windows.Forms

let getValues (a : 'U) (b : 'V) = sprintf "%A %A" a b

let exercise = new System.Windows.Forms.Form(Text = "Values", Width = 230, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12, Width = 150)
exercise.Controls.Add txtValue

let genders = [| "Male"; "Female" |]
let weight = 145.75
txtValue.Text <- (getValues genders weight)

System.Windows.Forms.Application.Run exercise

This would produce:

Passing Generic Arguments

Creating a Generic Function

Although most functions in F# are primarily considered generic, when creating a function, you can formally specify that it is generic. To do this, just after the name of the function, type <>. Inside those symbols, type an apostrophe and a letter or word. Here is an example:

let display<'T> value = sprintf "Value: %A" value

Calling a Generic Function

You have two options to call a generic function. You can call it just as we have done so far, simply using the name of the function. Here is an example:

open System
open System.Windows.Forms

let display<'T> value = sprintf "Number: %A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width = 150, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12, Width = 70)
exercise.Controls.Add txtValue

let a = 25
txtValue.Text <- (display a)

System.Windows.Forms.Application.Run exercise

This would produce:

Calling a Generic Function

Here is another run of the program:

open System
open System.Windows.Forms

let display<'T> value = sprintf "Gender: %A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width = 150, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12, Width = 70)
exercise.Controls.Add txtValue

let gender = 'M'
txtValue.Text <- (display gender)

System.Windows.Forms.Application.Run exercise

This would produce:

Calling a Generic Function

If the function is called many times in the program, then you must include the paremeter in parentheses followed by : ' and the same letter or word applied to the generic function. Here is an example:

open System
open System.Windows.Forms

type Category =
| Condominium
| Townhouse
| SingleFamily

let display<'T> (value : 'T) = sprintf "%A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=300, Height=130)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 48, Text = "Value 1:"))
let txtValue1 = new TextBox(Left = 65, Top = 12, Width = 70)
exercise.Controls.Add txtValue1

exercise.Controls.Add(new Label(Left = 12, Top = 43, Width = 48, Text = "Value 2:"))
let txtValue2 = new TextBox(Left = 65, Top = 40, Width = 150)
exercise.Controls.Add txtValue2

exercise.Controls.Add(new Label(Left = 12, Top = 70, Width = 48, Text = "Value 3:"))
let txtValue3 = new TextBox(Left = 65, Top = 70, Width = 210)
exercise.Controls.Add txtValue3

let a = 25
txtValue1.Text <- (display a)

let b = "Movie Production"
txtValue2.Text <- (display b)

let c = [| Condominium; Townhouse; SingleFamily |]
txtValue3.Text <- (display c)

System.Windows.Forms.Application.Run exercise

This would produce:

Calling a Generic Function

If the function takes more than one parameter and you want to apply the same type to all parameters, include each parameter in its parentheses with the common type. Here are examples:

open System
open System.Windows.Forms

let display<'T> (a : 'T) (b : 'T) = sprintf "%A, %A" a b

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=290, Height=130)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 48, Text = "Value 1:"))
let txtValue1 = new TextBox(Left = 65, Top = 12, Width = 70)
exercise.Controls.Add txtValue1

exercise.Controls.Add(new Label(Left = 12, Top = 43, Width = 48, Text = "Value 2:"))
let txtValue2 = new TextBox(Left = 65, Top = 40, Width = 200)
exercise.Controls.Add txtValue2

exercise.Controls.Add(new Label(Left = 12, Top = 70, Width = 48, Text = "Value 3:"))
let txtValue3 = new TextBox(Left = 65, Top = 70, Width = 200)
exercise.Controls.Add txtValue3

let a = 25
let b = 48
txtValue1.Text <- (display a b)

let x = "Video Category"
let y = "British Comedy"
txtValue2.Text <- (display x y)

let u = [| true; false |]
let v = [| false; false; true; false |]
txtValue3.Text <- (display u v)

System.Windows.Forms.Application.Run exercise

This would produce:

Calling a Generic Function

The Data Type of a Generic Parameter

Another technique used to call a generic function is to specify the data type of a generic argument. To do this, on the right side of the function, type <>. Inside the operator, enter the data type. Here is an example:

open System
open System.Windows.Forms

let display<'T> value = sprintf "Number: %A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width = 150, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12, Width = 70)
exercise.Controls.Add txtValue

let a = 25
txtValue.Text <- (display<int> a)

System.Windows.Forms.Application.Run exercise

Here is another run of the program:

open System
open System.Windows.Forms

let display<'T> value = sprintf "%A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width = 240, Height=75)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 40, Text = "Value:"))
let txtValue = new TextBox(Left = 52, Top = 12, Width = 160)
exercise.Controls.Add txtValue

let a = "The Distinguished Gentleman"
txtValue.Text <- (display<string> a)

System.Windows.Forms.Application.Run exercise

This would produce:

Passing Generic Arguments

In the same way, whenever you call the function, on the right side of the function, you can specify the data type of its argument. Here is an example:

open System
open System.Windows.Forms

type Category =
| Condominium
| Townhouse
| SingleFamily

let display<'T> (value : 'T) = sprintf "%A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=300, Height=130)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 48, Text = "Value 1:"))
let txtValue1 = new TextBox(Left = 65, Top = 12, Width = 70)
exercise.Controls.Add txtValue1

exercise.Controls.Add(new Label(Left = 12, Top = 43, Width = 48, Text = "Value 2:"))
let txtValue2 = new TextBox(Left = 65, Top = 40, Width = 150)
exercise.Controls.Add txtValue2

exercise.Controls.Add(new Label(Left = 12, Top = 70, Width = 48, Text = "Value 3:"))
let txtValue3 = new TextBox(Left = 65, Top = 70, Width = 210)
exercise.Controls.Add txtValue3

let a = 25
txtValue1.Text <- (display<int> a)

let b = "Movie Production"
txtValue2.Text <- (display<string> b)

let c = [| Condominium; Townhouse; SingleFamily |]
txtValue3.Text <- (display<Category[]> c)

System.Windows.Forms.Application.Run exercise

You can also ask the compiler to figure out what the actual data type of the parameter is. To do this, use the underscore wildcard in place of the parameter. Here are examples:

open System
open System.Windows.Forms

type Category =
| Condominium
| Townhouse
| SingleFamily

let display<'T> (value : 'T) = sprintf "%A" value

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=300, Height=130)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 48, Text = "Value 1:"))
let txtValue1 = new TextBox(Left = 65, Top = 12, Width = 70)
exercise.Controls.Add txtValue1

exercise.Controls.Add(new Label(Left = 12, Top = 43, Width = 48, Text = "Value 2:"))
let txtValue2 = new TextBox(Left = 65, Top = 40, Width = 150)
exercise.Controls.Add txtValue2

exercise.Controls.Add(new Label(Left = 12, Top = 70, Width = 48, Text = "Value 3:"))
let txtValue3 = new TextBox(Left = 65, Top = 70, Width = 210)
exercise.Controls.Add txtValue3

let a = 25
txtValue1.Text <- (display<_> a)

let b = "Movie Production"
txtValue2.Text <- (display<_> b)

let c = [| Condominium; Townhouse; SingleFamily |]
txtValue3.Text <- (display<_> c)

System.Windows.Forms.Application.Run exercise

The Data Types of Generic Parameters

If the function is taking more than one generic argument, when calling the function, if you want to specify the data type of each generic argument, on the right side of the name of the function, type <>. Inside the operator, enter each data type and separate them with commas. You can omit the generic letters or words on the right side of the name of the function when defining it. Here is an example:

let addition (a : 'U) (b : 'V) = sprintf "%A %A" a b

let x = 248
let y = 75
// Passing two integers
addition<int, int> x y

let c = "Catherine Watts"
let d = 26;
// Passing a string and an integer
addition<string, int> c d

let status = "Full-Time Employee"
let truth = true
// Passing a string and a Boolean value
addition<string, bool> status truth

let n = 282.74
let m = 60
// Passing a decimal and a natural number
addition<float, int> n m

And you must specify the generic letters or words on the right side of the name of the function when defining it. Here is an example:

open System
open System.Windows.Forms

let display<'U, 'V> (a : 'U) (b : 'V) = sprintf "%A, %A" a b

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=290, Height=130)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 48, Text = "Value 1:"))
let txtValue1 = new TextBox(Left = 65, Top = 12, Width = 70)
exercise.Controls.Add txtValue1

exercise.Controls.Add(new Label(Left = 12, Top = 43, Width = 48, Text = "Value 2:"))
let txtValue2 = new TextBox(Left = 65, Top = 40, Width = 200)
exercise.Controls.Add txtValue2

exercise.Controls.Add(new Label(Left = 12, Top = 70, Width = 48, Text = "Value 3:"))
let txtValue3 = new TextBox(Left = 65, Top = 70, Width = 200)
exercise.Controls.Add txtValue3

let a =  75
let b = 248
// Passing two integers
txtValue1.Text <- (display<int, int> a b)

let x = "Catherine Watts"
let y = 26
// Passing a string and an integer
txtValue2.Text <- (display<string, int> x y)

let status = "Full-Time Employee"
let truth = true
// Passing a string and a Boolean value
txtValue3.Text <- (display<string, bool> status truth)

System.Windows.Forms.Application.Run exercise

You can also ask the compiler to figure out type one, a few, or all of the parameters is (are). To do this, when calling the function, use the underscore wildcard in the placeholder of the parameter. Here are examples:

open System
open System.Windows.Forms

let display<'U, 'V> (a : 'U) (b : 'V) = sprintf "%A, %A" a b

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=290, Height=130)

exercise.Controls.Add(new Label(Left = 12, Top = 15, Width = 48, Text = "Value 1:"))
let txtValue1 = new TextBox(Left = 65, Top = 12, Width = 70)
exercise.Controls.Add txtValue1

exercise.Controls.Add(new Label(Left = 12, Top = 43, Width = 48, Text = "Value 2:"))
let txtValue2 = new TextBox(Left = 65, Top = 40, Width = 200)
exercise.Controls.Add txtValue2

exercise.Controls.Add(new Label(Left = 12, Top = 70, Width = 48, Text = "Value 3:"))
let txtValue3 = new TextBox(Left = 65, Top = 70, Width = 200)
exercise.Controls.Add txtValue3

let a =  75
let b = 248
// Passing two integers
txtValue1.Text <- (display<_, int> a b)

let x = "Catherine Watts"
let y = 26
// Passing a string and an integer
txtValue2.Text <- (display<string, _> x y)

let status = "Full-Time Employee"
let truth = true
// Passing a string and a Boolean value
txtValue3.Text <- (display<_, _> status truth)

System.Windows.Forms.Application.Run exercise

Using Generic and Non-Generic Types in a Function

You can create a function that uses a specific data type for one or more parameters and one or more generic parameters. Here is an example:

open System
open System.Windows.Forms

let display<'T>  a (b : 'T) = sprintf "%s: %A" a b

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=200, Height=125)

let lblValue1 = new Label(Left = 12, Top = 15, Width = 200)
exercise.Controls.Add lblValue1

let lblValue2 = new Label(Left = 12, Top = 43, Width = 200)
exercise.Controls.Add lblValue2

let lblValue3 = new Label(Left = 12, Top = 70, Width = 200)
exercise.Controls.Add lblValue3

lblValue1.Text <- (display "Number" 5)
lblValue2.Text <- (display "Distance" 8.06)
lblValue3.Text <- (display "Video Status" "Movie Production")

System.Windows.Forms.Application.Run exercise

This would produce:

Using Generic and Non-Generic Types in a Function

In fact, you can indicate (a) specific type for the parameter(s) whose type(s) is(are) known. Here is an example:

open System
open System.Windows.Forms

let display<'T>  (a : string) (b : 'T) = sprintf "%s: %A" a b

let exercise = new System.Windows.Forms.Form(Text = "Values", Width=200, Height=125)

let lblValue1 = new Label(Left = 12, Top = 15, Width = 200)
exercise.Controls.Add lblValue1

let lblValue2 = new Label(Left = 12, Top = 43, Width = 200)
exercise.Controls.Add lblValue2

let lblValue3 = new Label(Left = 12, Top = 70, Width = 200)
exercise.Controls.Add lblValue3

lblValue1.Text <- (display "Number" 5)
lblValue2.Text <- (display "Distance" 8.06)
lblValue3.Text <- (display "Video Status" "Movie Production")

System.Windows.Forms.Application.Run exercise
     
 
 

Generic Records

     

Introduction

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

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

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

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

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

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

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

printfn "%A" Ecuador

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

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

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

printfn "%A" Ecuador

Generic Classes

 

Introduction

Consider the following class:

type HotelRoom(number) =
    member this.RoomNumber = number

The constructor of this class takes takes one parameter and assigns it to a member variable in the body of the class. Neither the constructor nor the property specify the type of value the parameter will carry or use. When creating a class, you may want to have one or more members whose type(s) is(are) not known at the time the class is created. This is typical for a class that would be used to create a list. That is, you may want to create a list-based class (also called a collection class) that can be used to create lists of any kinds.

A generic class is one who main member variable is not known in advance.

Creating a Generic Class

The above class indicates, as we saw with functions, that any class in F# is generic by default. Still, you can formally create a generic class when necessary. To create a generic class, after the name of the class, type <>. Inside the operator, type a letter or word preceded by '. This would represent the generic type, indicating that the data type that will be used is not determined at this time. In the parentheses of any constructor that uses a parameter that will use the generic type, follow the name of the parameter with ' and the generic letter or word. In the body of the class, use the parameter as you see fit. Here is an example:

type HotelRoom<'T>(number : 'T) =
    member this.RoomNumber = number

When creating an object, you can call the constructor of the class by simply using its name. As an alternative when declaring a variable from the class, to indicate that the constructor is using a generic parameter, on the right side of the name of the class, type <> and include the appropriate data type used by the parameter. Here is an example:

type HotelRoom<'T>(number : 'T) =
    member this.RoomNumber : 'T = number
        
let room = new HotelRoom<int>(105)

Here is another version of creating an object from the class:

type HotelRoom<'T>(number : 'T) =
    member this.RoomNumber : 'T = number
        
let room = new HotelRoom<string>("C-215")

A generic class can have various members that use the same type. In this case, in the parentheses of the constructor, enter each paremeter followed by : ' and the generic letter or word. Eventually, in the body of the class, use each parameter as you see fit.

type BillCollector<'T>(info : 'T, add : 'T) =
    class
        member this.GottenBack = info
        member this.Additional = add
    end

If you are planning to use a generic value in a particular operation, you must cast that value. Here is an example:

open System
open System.Windows.Forms

type HotelRoom<'T>(number : 'T, rate : 'T) =
    member this.RoomNumber : 'T = number
    member this.DailyRate : 'T = rate
    member this.CalculateInvoice days dayRate phoneCharge discountRate =
        let totalByDay = days * dayRate
        let discountAmount = totalByDay * discountRate / 100.00
        totalByDay + phoneCharge - discountAmount
        
let room = new HotelRoom<int>(105, 98)

// Form: Hotel Management
let hotelManagement = new Form(Width = 265, Height = 215, Text = "Hotel Management")

// Label: Room Number
hotelManagement.Controls.Add(new Label(Left = 18, Top = 21, Width = 80, Text = "Room #:"))

// Text Box: Room Number
let txtRoomNumber = new TextBox(Left = 105, Top = 18, Width = 46)
hotelManagement.Controls.Add txtRoomNumber

// Label: Daily Rate
hotelManagement.Controls.Add(new Label(Left = 18, Top = 47, Width = 80, Text = "Daily Rate:"))

// Text Box: Daily Rate
let txtDailyRate = new TextBox(Left = 105, Top = 45, Width = 75)
hotelManagement.Controls.Add txtDailyRate

// Label: Days Occupied
hotelManagement.Controls.Add(new Label(Left = 18, Top = 73, Width = 85, Text = "Days Occupied:"))

// Text Box: Days Occupied
let txtDaysOccupied = new TextBox(Left = 105, Top = 70, Width = 75, Text = "3")
hotelManagement.Controls.Add txtDaysOccupied

// Label: Phone Use
hotelManagement.Controls.Add(new Label(Left = 18, Top = 100, Width = 80, Text = "Phone Use:"))

// Text Box: Phone Use
let txtPhoneUse = new TextBox(Left = 105, Top = 96, Width = 75, Text = "15.42")
hotelManagement.Controls.Add txtPhoneUse

// Label: Amount Owed
hotelManagement.Controls.Add(new Label(Left = 18, Top = 154, Width = 80, Text = "Amount Owed:"))

// Text Box: Amount Owed
let txtAmountOwed = new TextBox(Left = 105, Top = 151, Width = 75, Text = "0.00")
hotelManagement.Controls.Add txtAmountOwed

// Button: Calculate
let btnCalculate = new Button(Left = 105, Top = 122, Text = "Calculate")

let strRoomNumber = sprintf "%i" room.RoomNumber
let strDailyRate = sprintf "%i" room.DailyRate

txtRoomNumber.Text <- strRoomNumber
txtDailyRate.Text <- strDailyRate

let btnCalculateClick e =
    let rmNbr = txtRoomNumber.Text
    let dailyRate = txtDailyRate.Text
    let days = float txtDaysOccupied.Text
    let phone = float txtPhoneUse.Text

    let room = new HotelRoom<string>(rmNbr, dailyRate)
    let total = room.CalculateInvoice days (float dailyRate) phone 0.00
    
    let strAmountOwed = sprintf "%0.02f" total
    
    txtAmountOwed.Text <- strAmountOwed

btnCalculate.Click.Add btnCalculateClick
hotelManagement.Controls.Add btnCalculate

do Application.Run hotelManagement

Here is an example of running the program:

Generic Classes

A Generic Class With Various Types

A generic class can use different generic types of values. To specify those types, In the <> operator, type a letter or a word preceded by ' for each paramater. In the parentheses of the constructor, apply the appropriate letter or word to the desired parameter. In the body of the class, use each parameter as you judge necessary. Here is an example:

type HotelRoom<'U, 'V>(number : 'U, rate : 'V) =
    member this.RoomNumber : 'U = number
    member this.DailyRate : 'V = rate
    member this.CalculateInvoice days dayRate phoneCharge discountRate =
        let totalByDay = days * dayRate
        let discountAmount = totalByDay * discountRate / 100.00
        totalByDay + phoneCharge - discountAmount

When you create an object from the class, on the right side of the constructor, add the <> operator. Inside that operator, enter the actual data types that the arguments use, separated by commas. In the parentheses of the constructor, pass the arguments, each depending on the corresponding type of the <> operator. Here is an example:

open System
open System.Windows.Forms

type HotelRoom<'U, 'V>(number : 'U, rate : 'V) =
    member this.RoomNumber : 'U = number
    member this.DailyRate : 'V = rate
    member this.CalculateInvoice days dayRate phoneCharge discountRate =
        let totalByDay = days * dayRate
        let discountAmount = totalByDay * discountRate / 100.00
        totalByDay + phoneCharge - discountAmount
        
let room = new HotelRoom<string, float>("C-210", 449.95)

// Form: Hotel Management
let hotelManagement = new Form(Width = 265, Height = 215, Text = "Hotel Management")

// Label: Room Number
hotelManagement.Controls.Add(new Label(Left = 18, Top = 21, Width = 80, Text = "Room #:"))

// Text Box: Room Number
let txtRoomNumber = new TextBox(Left = 105, Top = 18, Width = 46)
hotelManagement.Controls.Add txtRoomNumber

// Label: Daily Rate
hotelManagement.Controls.Add(new Label(Left = 18, Top = 47, Width = 80, Text = "Daily Rate:"))

// Text Box: Daily Rate
let txtDailyRate = new TextBox(Left = 105, Top = 45, Width = 75)
hotelManagement.Controls.Add txtDailyRate

// Label: Days Occupied
hotelManagement.Controls.Add(new Label(Left = 18, Top = 73, Width = 85, Text = "Days Occupied:"))

// Text Box: Days Occupied
let txtDaysOccupied = new TextBox(Left = 105, Top = 70, Width = 75, Text = "1")
hotelManagement.Controls.Add txtDaysOccupied

// Label: Phone Use
hotelManagement.Controls.Add(new Label(Left = 18, Top = 100, Width = 80, Text = "Phone Use:"))

// Text Box: Phone Use
let txtPhoneUse = new TextBox(Left = 105, Top = 96, Width = 75, Text = "0.00")
hotelManagement.Controls.Add txtPhoneUse

// Label: Amount Owed
hotelManagement.Controls.Add(new Label(Left = 18, Top = 154, Width = 80, Text = "Amount Owed:"))

// Text Box: Amount Owed
let txtAmountOwed = new TextBox(Left = 105, Top = 151, Width = 75, Text = "0.00")
hotelManagement.Controls.Add txtAmountOwed

// Button: Calculate
let btnCalculate = new Button(Left = 105, Top = 122, Text = "Calculate")

let strRoomNumber = sprintf "%s" room.RoomNumber
let strDailyRate = sprintf "%0.02f" room.DailyRate

txtRoomNumber.Text <- strRoomNumber
txtDailyRate.Text <- strDailyRate

let btnCalculateClick e =
    let rmNbr = txtRoomNumber.Text
    let dailyRate = txtDailyRate.Text
    let days = float txtDaysOccupied.Text
    let phone = float txtPhoneUse.Text

    let room = new HotelRoom<string, float>(rmNbr, float dailyRate)
    let total = room.CalculateInvoice days (float dailyRate) phone 0.00
    
    let strAmountOwed = sprintf "%0.02f" total
    
    txtAmountOwed.Text <- strAmountOwed

btnCalculate.Click.Add btnCalculateClick
hotelManagement.Controls.Add btnCalculate

do Application.Run hotelManagement

Here is an example of running the program:

A Generic Class With Various Types

In the same way, when you create a class, you can use as many generic types as you want in your class. You can also use a mixture of generic and non-generic parameters. In this case, create the generic types in the <> operator. In the parentheses of the class, include as many parameters as you want. Each generic parameter must be accompanied by its generic type. In the body of the class, use the parameters as you judge necessary. Here is an example:

type BillCollector<'U, 'V>(cat : 'U, item : string, add : 'V) =
    class
        member this.Category   = cat
        member this.GottenBack = item
        member this.Additional = add
    end

When creating the object, after starting to declare the variable, on the right side of the constructor, add <> in which you must specify (only) the actual type(s) of the generic parameter(s). Here are examples:

type BillCollector<'U, 'V>(cat : 'U, item : string, add : 'V) =
    class
        member this.Category   = cat
        member this.GottenBack = item
        member this.Additional = add
    end
    
let coll = new BillCollector<int, bool>(1, "Honda Accord 2002", true)

Here is another run of the program:

type BillCollector<'U, 'V>(cat : 'U, item : string, add : 'V) =
    class
        member this.Category   = cat
        member this.GottenBack = item
        member this.Additional = add
    end
    
let coll=BillCollector<string, float>("Unresponsive","Boat - Yamaha 212SS",22480.00);

Generic Methods

A method is referred to as generic if it takes a generic parameter. Of course, the generic type must have been defined in the name of the class. On the right side of the name of the method, apply the same generic type. Here is an example:

type BillCollector<'T>(item : 'T) =
    member this.GottenBack = item
    member this.Show<'T> (i : 'T) = sprintf "%A" i  

When calling the method, pass the same type of argument you passed to the constructor when creating an object. Here is an example:

type BillCollector<'T>(item : 'T) =
    class
        member this.GottenBack = item
        member this.Show<'T> (i : 'T) =
            sprintf "%A" i
    end
    
let coll = BillCollector<string>("Honda Accord 2002")
let result = sprintf "Vehicle Collected: %s" coll.GottenBack
coll.Show "Reason: Payment Delinquency"

Constraints on Generics

In all places where we have used generic types so far, almost any value could be used. In some cases, when creating a generic function or a generic class, you may want to put a restriction on the types of values that can be used on the generic parameter. Putting a restriction on a generic parameter is referred to as constraining the generic type. Generic constraining is done using a keyword named when. You have various options.

To indicate that a parameter type of a function or class must be based on an object created from a structure, in the <> operator of the function name or of the class name, after the generic letter or word, type when followed by the same generic type, followed by : struct. Here are examples:

let display<'T when 'T : struct> value = ...;

type Calculation<'T when 'T : struct>(x : 'T, y : 'T) =
    class

    end

To indicate that a parameter type of a function or class must be based on an object created from a class, in the <> operator, use not struct. Here are examples:

let display<'T when 'T : not struct> value = ...;

type Calculation<'T when 'T : not struct>(x : 'T, y : 'T) =
    class

    end

To indicate that a parameter type can allow null values, in the <> operator, instead of struct, use null. Here are examples:

let display<'T when 'T : null> value = ...;

type Calculation<'T when 'T : null>(x : 'T, y : 'T) =
    class

    end

To indicate that Boolean comparisons can be performed on the values of the generic type, use comparison. Here are examples:

let display<'T when 'T : comparison> value = ...;

type Calculation<'T when 'T : comparison>(x : 'T, y : 'T) =
    class

    end

To indicate that the comparison for equality can be performed on the values of the generic type, use equality. Here are examples:

let display<'T when 'T : equality> value = ...;

type Calculation<'T when 'T : equality>(x : 'T, y : 'T) =
    class

    end

You can specify that only values or objects of a certain class can be used as parameters for a certain function or class. Of course, you must have a class. You can use an existing class or you can first create one. Once you know the class you want to use as the basis for a generic type, to put the constraint, in the <> operator of the class name, after the generic letter or word, type when followed by the same generic type, the :> operator, and the name of the class. Here are examples:

type Numeric(a, b) =
    member this.Add = a + b
    member this.Multiply = a * b
    member this.Subtract = a - b
    member this.Divide =
        if b <> 0 then
            a / b
        else
            0
    
type Calculation<'T when 'T :> Numeric> (u : 'T) =
    member this.Show = u

When creating an object, specify the data type as the class you had specified as the generic constraint. Here is an example:

type Numeric(a, b) =
    member this.Add = a + b
    member this.Multiply = a * b
    member this.Subtract = a - b
    member this.Divide =
        if b <> 0 then
            a / b
        else
            0
    
type Calculation<'T when 'T :> Numeric> (u : 'T) =
    member this.Show = u;
    
let oper = Numeric(15, 49);
let result = Calculation<Numeric>(oper);
   
   
 

Home Copyright © 2015, FunctionX Home