Home

F# Collections: Lists

 

Fundamentals of Lists

 

Introduction to Lists

A list is a group of constant values of the same type. Both the F# language and the .NET Framework have classes that support collections. They both address the issues slightly differently. For example, collections supported by the F# libraries are immutable. This means that, when creating a collection, you must know and provide the items it will contain, and, by default, once the collection has been created, you cannot change it (unless you decide to create a new collection).

F# supports collections from the perspective of a functional language. The collection classes in the .NET Framwork were created with object-oriented programming in mind, and there is a class for almost any type of collection you would need.

Introduction to Creating a List

To create a list, start with an opening square bracket "[" and end with a closing square bracket "]". Inside the brackets, create the members of the list. Each member ends with a semicolon. Here are examples:

// Numbers
[ 12; 22; 48; 115; 306; ];
// Characters
[ 'c'; 'd'; 'g'; 'j'; 'm'; ];
// Names
[ "Frank"; "James"; "Joshua"; "Mary"; ];

The semicolon on the last member is optional. On the other hand, you don't have to create all members on the same line. In fact, you can write the square brackets on their own lines and the members on a separate line after the opening square bracket. Here is an example:

[
"Frank"; "James"; "Joshua"; "Mary";
];

Although you don't have to indent, indentation makes the list easier to read:

[
    "Frank"; "James"; "Joshua"; "Mary";
];

Another alternative is to have each member of the list on its own line. Here is an example:

[
    "Frank";
    "James";
    "Joshua";
    "Mary";
];

If you decide to create each member on its own line, you can omit the semicolons. Here are examples:

[
    "Frank"
    "James"
    "Joshua"
    "Mary"
];

An Empty List

An empty list is one that doesn't have any item, but it is still considered a list. To create an empty list, use the square brackets but leave them empty. Here is an example:

[]

Naming a List

A list is a kind of variable. Although you can use it without naming it, if you plan to refer to it in different parts of your code, you should give it a name. To do this, declare a variable using the let keyword, followed by a name for the variable, and assign the list to it. Here is an example:

let numbers = [ 12; 22; 48; 115; 306 ];

Remember that the items can be separated from the square brackets by different lines. Here is an example:

let characters = [
    'c'; 'd'; 'g'; 'j'; 'm'
];

To store an empty list in a variable, assign empty square brackets to it. Here is an example:

let numbers = []

Creating a Simple Range

A list is a series of arranged items. If you use the items that are known in advance and they are consecutive, you can specify only the beginning and end of the series. When creating the list, in the square brackets, type the starting item, followed by .., and followed by the ending item. It is a good idea to put a space before and after .. to make it easier to read. Here are examples:

// A range of numbers
[ 2 .. 8 ];
// A range of letters
[ 'h' .. 'q' ];

Introduction to Lists and Looping

 

Creating a Range From a Loop

Instead of creating a range from known constant values, you can use a for loop that indicates only the starting and ending values. The basic formula to follow is:

[ for VariableName in Range -> body ]

The VariableName can be any lowercase letter or any name. The Range is usually specified as Start Value .. End Value. The body of the loop is created after the -> sign. It can simply consist of the VariableName. Here is an example:

[ for variable in 1 .. 8 -> variable ]

This is equivalent to numbers from 1 to 8:

[1; 2; 3; 4; 5; 6; 7; 8]

Yielding an Item From a Loop

An alternative to use the -> operator is to use the do yield expression. Here is an example:

[ for variable in 1 .. 8 do yield variable ]

A Skip Range

When you create a range, you specify the starting and the ending values. When the range is processed, the compiler will add 1 to the first value to get the second value. Then it would add 1 to each subsequent value to get the next value, until that next value is immediately less than or equal to the ending value. Instead of adding 1 to each value to get the next, you may want to use another incrementing scheme. That type of range is created using the skip range operator representd by .. ..

The formula to create a skip range is:

Start .. Step .. End

The algorithm works as follows:

  • Start is the value by which to start
  • End is the value by which the count will stop
  • Then:
    • Step is first value added to Start to get the New value
    • The New value is compared to the End value. If the New value is lower than the End value + Step, Step is added again to the New value
    • Once the New value becomes higher than the End value, the operation stops

Here is an example:

[3 .. 2 .. 20 ]

In this case, the range would start at 3. The value 2 would be added to the first, 3, to get the next. Then 2 would be added to the new value, and so on, until the new number is immediately lower than the last number specified in the range:

Numbers: [ 3; 5; 7; 9; 11; 13; 15; 17; 20 ]

As a consequence, the End value is included in the result if it gets out of range when the step is added to the previous value.

Most of the time, you will create a looping range because you want the numbers to follow a certain pattern instead of simply incrementing. Here is an example:

[ for variable in 0 .. 15 -> variable * 5 ]

Creating a List From a Sequence Expression

Although each member of a list must be a constant value, each value can be created from a sequence, that is, values created from an expression so that the values would follow a pattern. Here is an example:

[ for variable in 0 .. 15 do yield variable * 5 ]

Including a Conditional Statement to Yield a List

If you want to use a conditional statement that selects the values to include in a list, the statement can be included between the do and the yield keywords.

Using a List

 

Introduction

To present a list of items, you can use %A as the placeholder of the sprintf() function. Instead for first declaring the variable, we saw that you can create a name-less list. In this case, you can provide that list directly in the placeholder. Either way, this means that you don't have to first declare a variable for a list. You can just create the list directly where it is needed.

You use the same technique for a skip-range.

For Each Item in a List

To help you visit each member of a list, the F# language provides a looping operation that uses the for keyword. The formula to follow is:

for identifier / pattern in list do
   body

The for, the in, and the do keywords are required. The identifier could be a variable name that will hold the value to be retrieved from the list. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

// Form: Australian States
let australia : Form = new Form(Text = "Australia", StartPosition = FormStartPosition.CenterScreen,
                                ClientSize = new System.Drawing.Size(208, 108), MaximizeBox = false)

// States
australia.Controls.Add(new Label(AutoSize = true, Location = new Point(12, 18), Text = "States:"))
let lbxStates : ListBox = new ListBox(Location = new Point(62, 17),
                                      Size = new System.Drawing.Size(128, 80))
australia.Controls.Add lbxStates

let states = [
    "Western Australia"
    "South Australia"
    "Queensland"
    "New South Wales"
    "Victoria" ]

let mutable i = 0

for state in states do
    i <- lbxStates.Items.Add state

australia.Controls.Add lbxStates

[<EntryPoint>]
let main argv =
    Application.Run australia
    0

This would produce:

For Each Item in a List

A List in a List

In the lists we have created so far, we treated each item as a primitive type. In reality, each member of the list is considered a complete type and could be anything, including its own list. To make a list a member of another list, in the placeholder of the list, create the list just as we have done so far, using its own square brackets. If you decide to use this technique, all list items must be the same type of list but they can have different numbers of items. Here is an example:

let names = [ ["Joshua"; "Morrison"]; ["Frank"; "Aaron"; "Xaviera"]; ["James"; "Lalong"] ];

To make the code easier to read, you can type the individual list on their own lines. Here is an example:

let names =
    [
	["Joshua"; "Morrison"];
	["Frank"; "Aaron"; "Xaviera"];
	["James"; "Lalong"]
    ];
     
 

Introductory Operations on Lists

 

Checking Whether a List is Empty

We know that, to create an empty list, you can assign empty square brackets [] to it. Here is an example:

let numbers = []

To let you find out whether the list is empty, you can use the match operator with an option as []. If that option is valid, it means that the list of empty. The other option can be an underscore, which means that the list contains at least one item. Here are examples:

open System
open System.Drawing
open System.Windows.Forms 

// Form: Australian States
let australia : Form = new Form(Text = "Australia", StartPosition = FormStartPosition.CenterScreen,
                                ClientSize = new System.Drawing.Size(208, 108), MaximizeBox = false)

// States
australia.Controls.Add(new Label(AutoSize = true, Location = new Point(12, 18), Text = "States:"))
let lbxStates : ListBox = new ListBox(Location = new Point(62, 17),
                                      Size = new System.Drawing.Size(128, 80))
australia.Controls.Add lbxStates

let states = []

let mutable i = 0

match states with
| [] -> MessageBox.Show("There is no state to display", "Australian States",
                        MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
| _ ->
    for state in states do
        i <- lbxStates.Items.Add state

australia.Controls.Add lbxStates

[<EntryPoint>]
let main argv =
    Application.Run australia
    0

This would produce:

Checking Whether a List is Empty

Checking Whether a List is Empty

Attaching an Item to a List

As mentioned already, a list is immutable. As an alternative, you can add (a) new item(s) to the list but you would get a new, different list, that contains the original item(s) and the new one(s). To let you perform this operation, the List union is equipped with, or defines, an operator named cons and represented as ::. To use it, start with the let operator and a new name, assign the new value to it, followed by :: and the original list. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

// Form: Australian States
let australia : Form = new Form(Text = "Australia", StartPosition = FormStartPosition.CenterScreen,
                                ClientSize = new System.Drawing.Size(208, 115), MaximizeBox = false)

// States
australia.Controls.Add(new Label(AutoSize = true, Location = new Point(12, 18), Text = "States:"))
let lbxStates : ListBox = new ListBox(Location = new Point(62, 17),
                                      Size = new System.Drawing.Size(128, 85))
australia.Controls.Add lbxStates

let states = [
    "Western Australia"
    "South Australia"
    "Queensland"
    "New South Wales"
    "Victoria" ]

let totalLand = "Tasmania" :: states

let mutable i = 0

match states with
| [] -> MessageBox.Show("There is no state to display", "Australian States",
                        MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
| _ ->
    for state in totalLand  do
        i <- lbxStates.Items.Add state

australia.Controls.Add lbxStates

[<EntryPoint>]
let main argv =
    Application.Run australia
    0

This would produce:

Attaching an Item to a List

Keep in mind that the original list has not changed:

Concatenating Some Lists

Concatening two list consists of adding them. To perform this operation, use the @ operator in place of +. The formula to follow is:

list-1 @ list-2

The @ operator is used to add two lists. The list on the left of the operator, list-1, is added to the list on the right. The result produces a new list where the items of the left list will appear first, followed by the items of the list on the left. The lists must be compatible. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

// Form: Australian States
let australia : Form = new Form(Text = "States & Territories", StartPosition = FormStartPosition.CenterScreen,
                                ClientSize = new System.Drawing.Size(200, 140), MaximizeBox = false)

// States
let lbxStates : ListBox = new ListBox(Location = new Point(36, 17),
                                      Size = new System.Drawing.Size(132, 115))
australia.Controls.Add lbxStates

let states = [
    "Western Australia"
    "South Australia"
    "Queensland"
    "New South Wales"
    "Victoria" ]

let territories = [
    "Australian Capital Territory"
    "Jervis Bay Territory"
    "Northern Territory" ]

let mutable i = 0

match states with
| [] -> MessageBox.Show("There is no state to display", "Australian States",
                        MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
| _ ->
    for state in states @ territories  do
        i <- lbxStates.Items.Add state

australia.Controls.Add lbxStates

[<EntryPoint>]
let main argv =
    Application.Run australia
    0

This would produce:

Concatenating Some Lists

If you plan to use the result many times, you can store it in a variable. To do this, assign the concatenation to the variable. Here is an example:

let government = states @ territories

You can use a combination of attaching one or more items to a list and/or concatening lists to get a new list. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

// Form: Australian States
let australia : Form = new Form(Text = "States & Territories", StartPosition = FormStartPosition.CenterScreen,
                                ClientSize = new System.Drawing.Size(200, 155), MaximizeBox = false)

let lbxStates : ListBox = new ListBox(Location = new Point(36, 17),
                                      Size = new System.Drawing.Size(132, 125))
australia.Controls.Add lbxStates

let states = [
    "Western Australia"
    "South Australia"
    "Queensland"
    "New South Wales"
    "Victoria" ]

let territories = [
    "Australian Capital Territory"
    "Jervis Bay Territory"
    "Northern Territory" ]

let government = "Tasmania" :: states @ territories

let mutable i = 0

match states with
| [] -> MessageBox.Show("There is no state to display", "Australian States",
                        MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
| _ ->
    for state in government  do
        i <- lbxStates.Items.Add state

australia.Controls.Add lbxStates

[<EntryPoint>]
let main argv =
    Application.Run australia
    0

This would produce:

Concatenating Some Lists

Here is another example:

let states = [
    "Western Australia"
    "South Australia"
    "Queensland"
    "New South Wales"
    "Victoria" ]

let inLandTerritories = [
    "Australian Capital Territory"
    "Jervis Bay Territory"
    "Northern Territory" ]

let externalTerritories = [ "Australian Antarctic Territory";
                            "Christmas Island";
                            "Cocos (Keeling) Islands";
                            "Coral Sea Islands";
                            "Heard Island and McDonald Islands";
                            "Norfolk Island" ]

let government = "Tasmania" :: states @ inLandTerritories @ "Ashmore and Cartier Islands" :: externalTerritories

A Mutable List

In our introduction,  we saw that a list is immutable. That's the default case if you directly use the list where it is needed. There is no built-in mechanism to add a new item to a list except to create a new list that contains the existing items and the new one(s). If you create a list from a declared variable, you can make that variable mutable. Then you can use the :: operator to add a prefixed item to the list. That way, an existing list can be modified. Here is an example:

open System
open System.Windows.Forms

let exercise = new Form()
exercise.Text   <- "Work Days"
exercise.Width  <- 145
exercise.Height <- 160

let lbxNumbers = new ListBox()
lbxNumbers.Left   <- 18
lbxNumbers.Top    <- 18
lbxNumbers.Width  <- 100
lbxNumbers.Height <- 100

let mutable days = [ "Monday"; "Tuesday"; "Wednesday"; "Thursday"; "Friday"; "Saturday" ]

days <- "Sunday" :: days

for day in days do
    lbxNumbers.Items.Add day |> ignore

exercise.Controls.Add lbxNumbers

do Application.Run exercise

This would produce:

A Mutable List

In the same way, you can create a list as a reference cell. Here is an example:

open System
open System.Windows.Forms

let exercise = new Form()
exercise.Text   <- "Work Days"
exercise.Width  <- 145
exercise.Height <- 160

let lbxNumbers = new ListBox()
lbxNumbers.Left   <- 18
lbxNumbers.Top    <- 18
lbxNumbers.Width  <- 100
lbxNumbers.Height <- 100

let days = ref [ "Monday"; "Tuesday"; "Wednesday"; "Thursday"; "Friday"; "Saturday" ]

days := "Sunday" :: !days

for day in !days do
    lbxNumbers.Items.Add day |> ignore

exercise.Controls.Add lbxNumbers

do Application.Run exercise

In the same way, you can use the characteristics of the Ref built-in record. Here is an example:

open System
open System.Windows.Forms

let exercise = new Form()
exercise.Text   <- "Work Days"
exercise.Width  <- 145
exercise.Height <- 160

let lbxNumbers = new ListBox()
lbxNumbers.Left   <- 18
lbxNumbers.Top    <- 18
lbxNumbers.Width  <- 100
lbxNumbers.Height <- 100

let days = ref [ "Monday"; "Tuesday"; "Wednesday"; "Thursday"; "Friday"; "Saturday" ]

(:=) days ("Sunday" :: !days)

for day in (!) days do
    lbxNumbers.Items.Add day |> ignore

exercise.Controls.Add lbxNumbers

do Application.Run exercise

Lists and Functions

   

Creating Items From a Function

Although each member of a list must be a constant value, each value can be created from a function. Here is an example:

let add x = x + x
let numbers = [ for variable in 1 .. 8 do yield add variable ]

Of course, you can have a more complicated function than that. You can also generate values from the method of a class.

Returning a List from a Function

You can create a function that returns a list. To do this, make sure the last assignment of the function is a list. Here is an example:

open System
open System.Drawing
open System.Windows.Forms

let getLeapYears fromYear toYear =
    let mutable years = []
    
    for y in fromYear .. toYear do
        if DateTime.IsLeapYear(y) then
            years <- y :: years
    years

let statistics : Form = new Form(Text = "Statistics", StartPosition = FormStartPosition.CenterScreen,
                                ClientSize = new System.Drawing.Size(155, 245), MaximizeBox = false)

let lbxLeapYears : ListBox = new ListBox(Location = new Point(16, 17),
                                      Size = new System.Drawing.Size(120, 215))
statistics.Controls.Add lbxLeapYears

let leapYears = getLeapYears 1940 2000

let mutable i = 0

for year in leapYears  do
    i <- lbxLeapYears.Items.Add(sprintf "\t%i" year)

[<EntryPoint>]
let main argv =
    Application.Run statistics
    0

This would produce:

Returning a List from a Function

Just like a function can return one list, it can return as many as you want. For example, you can create a function that returns a list of lists.

A List as Parameter

A function can receive a parameter that is a list and treat it accordingly. Because F# is an inferred language, you can just pass the name of a parameter without a data type, and then use the parameter in the function as if that parameter were a list. Normally, if you do that, the compiler doesn't know what type of parameter the list is, and that parameter could be anything. When calling the function, pass a list as argument and the compiler will apply the appropriate rules based on the type of argument you passed. Here are examples:

open System
open System.Drawing
open System.Windows.Forms

let display list (lbxControl : ListBox) title =
    let mutable i = 0

    match list with
    | [] -> MessageBox.Show("There is no item to display", title,
                            MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore
    | _ ->
        for item in list do
            i <- lbxControl.Items.Add item

let australia : Form = new Form(Text = "States & Territories", StartPosition = FormStartPosition.CenterScreen,
                                ClientSize = new System.Drawing.Size(215, 245), MaximizeBox = false)

let lbxStates : ListBox = new ListBox(Location = new Point(16, 17),
                                      Size = new System.Drawing.Size(180, 215))
australia.Controls.Add lbxStates

let states = [
    "Western Australia"
    "South Australia"
    "Queensland"
    "New South Wales"
    "Victoria" ]

let inLandTerritories = [
    "Australian Capital Territory"
    "Jervis Bay Territory"
    "Northern Territory" ]

let externalTerritories = [ "Christmas Island";
                            "Ashmore and Cartier Islands";
                            "Coral Sea Islands";
                            "Heard Island and McDonald Islands";
                            "Norfolk Island";
                            "Australian Antarctic Territory" ]

let government = "Tasmania" :: states @ inLandTerritories @ "Cocos (Keeling) Islands" :: externalTerritories

display government lbxStates "Australian States & Territories"

[<EntryPoint>]
let main argv =
    Application.Run australia
    0

This would produce:

Concatenating Some Lists

As mentioned already, when creating a function that takes a list as parameter, you can just specify a name for the parameter. Since you know that the parameter is a list, in  the body of the function, treat the argument as a list, using all approriate rules of lists. For example, you can use a loop on the parameter.

Lists and Tuples

 

Creating a Range List From a Tuple

Tuples provide the ability to create a list of numbers as a range from two members of a tuple:

  • The tuple has to include two numeric or letter members
  • The first or left member has to be lower than the second or right member

When the .. operator is applied on both members, the result is a range that includes both values and the values between them. Here is an example of a function that creates a list  from a pair:

let range(a, b) = 
    [a .. b]

let result = range(2, 8);
MessageBox.Show(sprintf "List: %A" result, "List",
                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

This would produce:

Creating a Range List From a Tuple

You can also provide non-readable characters. In this case, the compiler would try to find their range. Here is an example:

let range(a, b) = [a .. b]

let result = range('$', '*');
MessageBox.Show(sprintf "List: %A" result, "List",
                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

This would produce:

Creating a Range List From a Tuple

Either way, if the compiler cannot find values between both members of the tuple, in which case it cannot create a range, the compiler would produce an empty range [].

Creating a Skip Range From a Tuple

A skip range is a technique of specifying the starting value of a range, the end value of a range, and the value to add to each value to get the next one. You can get such a skip-range from a tuple. Here is an example:

let range(a, b, c) = 
    [a .. b .. c];

let result = range(2, 3, 16);
MessageBox.Show(sprintf "List: %A" result, "List",
                MessageBoxButtons.OK, MessageBoxIcon.Information) |> ignore

This would produce:

Creating a Range List From a Tuple

A List of Tuples

A list can be made of tuples. In this case, each item in the list is specified  as a tuple. Here is an example where each item is a pair:

open System
open System.Drawing
open System.Windows.Forms 

let factors = [ (2.000, 6.43); (2.125, 6.49); (2.250, 6.55); (2.375, 6.60); (2.500, 6.66);
                (2.625, 6.72); (2.750, 6.78); (2.875, 6.84); (3.000, 6.90); (3.125, 6.96);
                (3.250, 7.02); (3.375, 7.08); (3.500, 7.14); (3.625, 7.21); (3.750, 7.27);
                (3.875, 7.33); (4.000, 7.39); (4.125, 7.45); (4.250, 7.52); (4.375, 7.58);
                (4.500, 7.64); (4.625, 7.71); (4.750, 7.77); (4.875, 7.84); (5.000, 7.90);
                (5.125, 7.97); (5.250, 8.03); (5.375, 8.10); (5.500, 8.18); (5.625, 8.24);
                (5.750, 8.31); (5.875, 8.37); (6.000, 8.44); (6.125, 8.51); (6.250, 8.58);
                (6.375, 8.64); (6.500, 8.72); (6.625, 8.78); (6.750, 8.85); (6.875, 8.92);
                (7.000, 8.99); (7.125, 9.06); (7.250, 9.13); (7.375, 9.20); (7.500, 9.27);
                (7.625, 9.34); (7.750, 9.41); (7.875, 9.48) ]

let amortizationFactor : Form = new Form(MaximizeBox = false, Text = "Amortization Table", ClientSize = new System.Drawing.Size(245, 445), StartPosition = FormStartPosition.CenterScreen)

amortizationFactor.Controls.Add(new Label(Text = "15-Year Amortization Table", Height = 13, Left = 12, Top = 12, Width = 150))

let lbxAmortizationTable : ListBox = new ListBox(Left = 12, Top = 32, Width = 220, Height = 400)
lbxAmortizationTable.Items.Add("\tInterest\tMonthly Payment") |> ignore
lbxAmortizationTable.Items.Add("    \tRate\tFactor") |> ignore

for factor in factors do
    let interestRate, paymentFactor = factor
    lbxAmortizationTable.Items.Add(sprintf "\t%0.03f\t%0.02f" interestRate paymentFactor) |> ignore

amortizationFactor.Controls.Add lbxAmortizationTable

do Application.Run amortizationFactor

This would produce:

A List of Tuples

A Tuple of Lists

A tuple can consist of elements that each is a list. Here is an example:

let yearComponents = ([ "Monday"; "Tuesday"; "Wednesday"; "Thursday"; "Friday"; "Saturday" ], [ "January"; "February"; "March" ])

As you should know already, to access each list, you can first declare variables that represent each element and assign the tuple to the combination. Here is an example:

let yearComponents = ([ "Monday"; "Tuesday"; "Wednesday"; "Thursday"; "Friday"; "Saturday" ],
                      [ "January"; "February"; "March" ])

let days, months = yearComponents

You can then use one or each list as you see fit.

Lists and Records

The members of a list can come from a record. You can first define a record, create objects from it, and use them as members of a list. Here is an example:

type Ocean = {
    Name         : string
    Area         : int
    Volume       : int
    AverageDepth : int
    CoastLine    : int }

let oceans = [ { Name = "Pacific "; Area = 168723000; Volume = 669880000; AverageDepth = 3970; CoastLine = 135663 }
               { Name = "Indian  "; Area = 70560000;  Volume = 264000000; AverageDepth = 3741; CoastLine = 66526  }
               { Name = "Atlantic"; Area = 85133000;  Volume = 310410900; AverageDepth = 3646; CoastLine = 111866 } 
               { Name = "Arctic  "; Area = 15558000;  Volume = 18750000;  AverageDepth = 1205; CoastLine = 45389  }
               { Name = "Southern"; Area = 21960000;  Volume = 71800000;  AverageDepth = 3270; CoastLine = 17968  }  ]

Of course, the record have labels of tuples or classes or records. Here is an example:

type OceanDepth = {
    AverageDepth    : int
    LowestPoint     : int
    DeepestLocation : string }
    
type Ocean = {
    Name         : string
    Area         : int * float
    Volume       : int * float
    Depth        : OceanDepth
    CoastLine    : int }

let oceans = [ { Name = "Pacific "; Area = (168723000, 46.6); Volume = (669880000, 50.1); Depth = { AverageDepth = 3970; LowestPoint = 10911; DeepestLocation = "Mariana Trench       " }; CoastLine = 135663 }
               { Name = "Indian  "; Area = ( 70560000, 23.5); Volume = (264000000, 23.3); Depth = { AverageDepth = 3741; LowestPoint =  8047; DeepestLocation = "Diamantina Trench    " }; CoastLine = 66526  }
               { Name = "Atlantic"; Area = ( 85133000, 19.5); Volume = (310410900, 19.8); Depth = { AverageDepth = 3646; LowestPoint =  8380; DeepestLocation = "Puerto Rico Trench   " }; CoastLine = 111866 } 
               { Name = "Arctic  "; Area = ( 15558000,  6.1); Volume = ( 18750000,  5.4); Depth = { AverageDepth = 1205; LowestPoint =  5450; DeepestLocation = "Litke Deep           " }; CoastLine = 45389 }
               { Name = "Southern"; Area = ( 21960000,  4.3); Volume = ( 71800000,  1.4); Depth = { AverageDepth = 3270; LowestPoint =  7236; DeepestLocation = "South Sandwich Trench" }; CoastLine = 17968 }  ]

After doing this, you can access each member of the list as an individual record.

Lists and Classes

 

A List of Objects

The members of a list can be anything, as long as all of them are of the same type. This means that you can use objects as members of a list. Of course, you must have a class. You can use one of the classes or structures of the .NET Framework or you can create your own class or structure. You can then create objects of your chosen class or structure and use those objects as members of a list. Here is an example:

type SaleItem(number, category, subcat, name, size, price, days) =
    let nbr = ref number
    let cat = ref category
    let sub = ref subcat
    let nm  = ref name
    let sz  = ref size
    let prc = ref price
    let ds  = ref days
    
    member this.ItemNumber  with get() = !nbr and set(value) = nbr := 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.DaysInStore with get() = !ds  and set(value) = ds  := value
    
    member this.DiscountRate with get() =
                                    if !ds >= 60 then 50.00
                                    elif !ds >= 30 then 35.00
                                    elif !ds >= 15 then 10.00
                                    else 0.00
    member this.DiscountAmount with get() = !prc * this.DiscountRate / 100.00    
    member this.MarkedPrice    with get() = this.UnitPrice - this.DiscountAmount
    new() = SaleItem(0, "", "", "", "", 0.00, 0)
   
let item726370 = new SaleItem(726370, "Women", "Dresses", "Surplice Fitness Dress", "Small", 24.85, 36)
let item934807 = new SaleItem(934807, "Baby Boy", "Shorts", "Cargo Shorts", "6 Months", 4.25, 5)
let item394669 = new SaleItem(394669, "Girls 4-6x", "Dresses", "Classics Tulle Dress", "5", 27.75, 22)

let salesItems = [ item726370; item934807; item394669 ]

You don't have to first declare individual variables, you can create the objects directly in the list. Here is an example:

let salesItems = [ new SaleItem(726370, "Women", "Dresses", "Surplice Fitness Dress", "Small", 24.85, 36)
                   new SaleItem(934807, "Baby Boy", "Shorts", "Cargo Shorts", "6 Months", 4.25, 5)
                   new SaleItem(394669, "Girls 4-6x", "Dresses", "Classics Tulle Dress", "5", 27.75, 22) ]

You can also create a list of objects from an existing list. Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

type StoreItem = {
    ItemNumber  : int
    Category    : string
    SubCategory : string
    ItemName    : string
    ItemSize    : string
    UnitPrice   : float
    DaysInStore : int
    Status      : string }

type SaleItem(number, category, subcat, name, size, price, days) =
    let nbr = ref number
    let cat = ref category
    let sub = ref subcat
    let nm  = ref name
    let sz  = ref size
    let prc = ref price
    let ds  = ref days
    
    member this.ItemNumber  with get() = !nbr and set(value) = nbr := 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.DaysInStore with get() = !ds  and set(value) = ds  := value
    
    member this.DiscountRate with get() =
                                    if !ds >= 60 then 50.00
                                    elif !ds >= 30 then 35.00
                                    elif !ds >= 15 then 10.00
                                    else 0.00
    member this.DiscountAmount with get() = !prc * this.DiscountRate / 100.00    
    member this.MarkedPrice    with get() = this.UnitPrice - this.DiscountAmount
    new() = SaleItem(0, "", "", "", "", 0.00, 0)
   
let storeItems = new List<StoreItem>()

storeItems.Add { ItemNumber = 692557; Category = "Women"; SubCategory = "Dresses"; ItemName = "Printed Scoopneck Dress"; ItemSize = "XS"; UnitPrice = 110.00; DaysInStore = 1; Status = "Flawless" }
storeItems.Add { ItemNumber = 833074; Category = "Girls"; SubCategory = "Shirt Dresses"; ItemName = "Girls 2-6x Striped Cotton Oxford Shirtdress"; ItemSize = "2T"; UnitPrice = 59.95; DaysInStore = 35; Status = "Flawless" }
storeItems.Add { ItemNumber = 337363; Category = "Women"; SubCategory = "Skirts"; ItemName = "Textured Pencil Skirt"; ItemSize = "0"; UnitPrice = 95.5; DaysInStore = 18; Status = "Flawless" }
storeItems.Add { ItemNumber = 777526; Category = "Women"; SubCategory = "Dresses"; ItemName = "Printed Scoopneck Dress"; ItemSize = "S"; UnitPrice = 110.00; DaysInStore = 1; Status = "Flawless" }
storeItems.Add { ItemNumber = 405708; Category = "Men";   SubCategory = "Pants"; ItemName = "Basic Twill Pants"; ItemSize = "30X30"; UnitPrice = 58.85; DaysInStore = 32; Status = "Damaged - Torn" }
storeItems.Add { ItemNumber = 116033; Category = "Women"; SubCategory = "Dresses"; ItemName = "Printed Scoopneck Dress"; ItemSize = "M"; UnitPrice = 110.00; DaysInStore = 1; Status = "Flawless" }
storeItems.Add { ItemNumber = 844115; Category = "Girls"; SubCategory = "Shirt Dresses"; ItemName = "Girls 2-6x Striped Cotton Oxford Shirtdress"; ItemSize = "3T"; UnitPrice = 59.95; DaysInStore = 35; Status = "Flawless" }
storeItems.Add { ItemNumber = 397299; Category = "Women"; SubCategory = "Dresses"; ItemName = "Printed Scoopneck Dress"; ItemSize = "L"; UnitPrice = 110.00; DaysInStore = 1; Status = "Flawless" }
storeItems.Add { ItemNumber = 311362; Category = "Men";   SubCategory = "Shoes"; ItemName = "Burnished Leather Monk Loafers"; ItemSize = "9"; UnitPrice = 245.5; DaysInStore = 8; Status = "Flawless" }
storeItems.Add { ItemNumber = 388479; Category = "Women"; SubCategory = "Dresses"; ItemName = "Printed Scoopneck Dress"; ItemSize = "XL"; UnitPrice = 110.00; DaysInStore = 1; Status = "Flawless" }
storeItems.Add { ItemNumber = 693886; Category = "Boys";  SubCategory = "T-Shirts"; ItemName = "Boys 2-7 Solid Cotton Tee"; ItemSize = "2T"; UnitPrice = 17.85; DaysInStore = 72; Status = "Flawless" }
storeItems.Add { ItemNumber = 584930; Category = "Women"; SubCategory = "Skirts"; ItemName = "Textured Pencil Skirt"; ItemSize = "2"; UnitPrice = 95.5; DaysInStore = 18; Status = "Damaged - Torn" }
storeItems.Add { ItemNumber = 436147; Category = "Women"; SubCategory = "Skirts"; ItemName = "Textured Pencil Skirt"; ItemSize = "4"; UnitPrice = 95.5; DaysInStore = 18; Status = "Flawless" }
storeItems.Add { ItemNumber = 118863; Category = "Boys";  SubCategory = "T-Shirts"; ItemName = "Boys 2-7 Solid Cotton Tee"; ItemSize = "3T"; UnitPrice = 17.85; DaysInStore = 72; Status = "Flawless" }
storeItems.Add { ItemNumber = 863733; Category = "Women"; SubCategory = "Skirts"; ItemName = "Textured Pencil Skirt"; ItemSize = "6"; UnitPrice = 95.5; DaysInStore = 18; Status = "Flawless" }
storeItems.Add { ItemNumber = 593847; Category = "Women"; SubCategory = "Shirts"; ItemName = "Petite Mandarin Collar Tank Top"; ItemSize = "X-Small"; UnitPrice = 135.5; DaysInStore = 27; Status = "Damaged - Torn" }
storeItems.Add { ItemNumber = 396620; Category = "Women"; SubCategory = "Skirts"; ItemName = "Textured Pencil Skirt"; ItemSize = "8"; UnitPrice = 95.5; DaysInStore = 18; Status = "Flawless" }
storeItems.Add { ItemNumber = 714660; Category = "Women"; SubCategory = "Skirts"; ItemName = "Textured Pencil Skirt"; ItemSize = "10"; UnitPrice = 95.5; DaysInStore = 18; Status = "Flawless" }
storeItems.Add { ItemNumber = 202953; Category = "Men";   SubCategory = "Pants"; ItemName = "Basic Twill Pants"; ItemSize = "36X30"; UnitPrice = 58.85; DaysInStore = 32; Status = "Damaged - Torn" }
storeItems.Add { ItemNumber = 949668; Category = "Men";   SubCategory = "Pants"; ItemName = "Basic Twill Pants"; ItemSize = "38X30"; UnitPrice = 58.85; DaysInStore = 32; Status = "Damaged - Torn" }
storeItems.Add { ItemNumber = 365224; Category = "Women"; SubCategory = "Handbags"; ItemName = "Fairfield Leopard Tote"; ItemSize = "One Size"; UnitPrice = 268.85; DaysInStore = 26; Status = "Worn" }
storeItems.Add { ItemNumber = 355968; Category = "Men";   SubCategory = "Pants"; ItemName = "Maine Slim Straight-Leg Pants"; ItemSize = "36X32"; UnitPrice = 145.00; DaysInStore = 64; Status = "Flawless" }
storeItems.Add { ItemNumber = 142104; Category = "Men";   SubCategory = "Pants"; ItemName = "Cotton Cargo Pants"; ItemSize = "Medium"; UnitPrice = 95.5; DaysInStore = 22; Status = "Flawless" }
storeItems.Add { ItemNumber = 117399; Category = "Men";   SubCategory = "Pants"; ItemName = "Cotton Cargo Pants"; ItemSize = "Large"; UnitPrice = 95.5; DaysInStore = 22; Status = "Flawless" }
storeItems.Add { ItemNumber = 263773; Category = "Boys";  SubCategory = "T-Shirts"; ItemName = "Boys 2-7 Solid Cotton Tee"; ItemSize = "5T"; UnitPrice = 17.85; DaysInStore = 72; Status = "Flawless" }
storeItems.Add { ItemNumber = 441184; Category = "Men";   SubCategory = "Shoes"; ItemName = "Burnished Leather Monk Loafers"; ItemSize = "10"; UnitPrice = 245.5; DaysInStore = 8; Status = "Flawless" }
storeItems.Add { ItemNumber = 660244; Category = "Women"; SubCategory = "Shoes"; ItemName = "Python Print Sandals"; ItemSize = "6"; UnitPrice = 99.95; DaysInStore = 6; Status = "Worn" }
storeItems.Add { ItemNumber = 242793; Category = "Girls"; SubCategory = "Dresses"; ItemName = "Girls 7-16 Cotton Mesh Polo Dress"; ItemSize = "Small"; UnitPrice = 49.5; DaysInStore = 15; Status = "Flawless" }
storeItems.Add { ItemNumber = 714681; Category = "Men";   SubCategory = "Shoes"; ItemName = "Burnished Leather Monk Loafers"; ItemSize = "10.5"; UnitPrice = 245.5; DaysInStore = 8; Status = "Flawless" }
storeItems.Add { ItemNumber = 744704; Category = "Girls"; SubCategory = "Dresses"; ItemName = "Girls 7-16 Striped Shirtdress"; ItemSize = "8"; UnitPrice = 64.75; DaysInStore = 35; Status = "Flawless" }
storeItems.Add { ItemNumber = 743330; Category = "Women"; SubCategory = "Shoes"; ItemName = "Python Print Sandals"; ItemSize = "8.5"; UnitPrice = 99.95; DaysInStore = 6; Status = "Flawless" }
storeItems.Add { ItemNumber = 477446; Category = "Girls"; SubCategory = "Dresses"; ItemName = "Girls 7-16 Cotton Mesh Polo Dress"; ItemSize = "Medium"; UnitPrice = 49.5; DaysInStore = 15; Status = "Damaged - Torn" }
storeItems.Add { ItemNumber = 205248; Category = "Men";   SubCategory = "Shoes"; ItemName = "Burnished Leather Monk Loafers"; ItemSize = "11"; UnitPrice = 245.5; DaysInStore = 8; Status = "Flawless" }
storeItems.Add { ItemNumber = 498250; Category = "Girls"; SubCategory = "Dresses"; ItemName = "Girls 7-16 Striped Shirtdress"; ItemSize = "10"; UnitPrice = 64.75; DaysInStore = 35; Status = "Damaged - Torn" }
storeItems.Add { ItemNumber = 184830; Category = "Women"; SubCategory = "Handbags"; ItemName = "Leather Lexington Shopper Tote"; ItemSize = "One Size"; UnitPrice = 145.00; DaysInStore = 16; Status = "Flawless" }
storeItems.Add { ItemNumber = 770524; Category = "Women"; SubCategory = "Shoes"; ItemName = "Slingback Point Toe Pumps"; ItemSize = "6.5"; UnitPrice = 98.75; DaysInStore = 3; Status = "Damaged - Torn" }
storeItems.Add { ItemNumber = 830366; Category = "Girls"; SubCategory = "Dresses"; ItemName = "Girls 7-16 Cotton Mesh Polo Dress"; ItemSize = "Large"; UnitPrice = 49.5; DaysInStore = 15; Status = "Flawless" }
storeItems.Add { ItemNumber = 812107; Category = "Women"; SubCategory = "Shoes"; ItemName = "Slingback Point Toe Pumps"; ItemSize = "7.5"; UnitPrice = 98.75; DaysInStore = 3; Status = "Flawless" }
storeItems.Add { ItemNumber = 524224; Category = "Men";   SubCategory = "Shoes"; ItemName = "Bahama Canvas and Leather Boat Shoes"; ItemSize = "10"; UnitPrice = 65.95; DaysInStore = 44; Status = "Worn" }
storeItems.Add { ItemNumber = 116040; Category = "Women"; SubCategory = "Shoes"; ItemName = "Slingback Point Toe Pumps"; ItemSize = "8.5"; UnitPrice = 98.75; DaysInStore = 58; Status = "Worn" }

let salesItems = [ for item in storeItems do if item.Status = "Flawless" then yield new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize, item.UnitPrice, item.DaysInStore) ]

let saveInventory() =
    Directory.CreateDirectory(@"C:\Fun Department Store") |> ignore
    let strStoreItemsFile : string =  @"C:\Fun Department Store\StoreItems.sis"

    let bfAutoParts = new BinaryFormatter()
    use fsAutoParts = new FileStream(strStoreItemsFile, FileMode.Create, FileAccess.Write, FileShare.Write)
    bfAutoParts.Serialize(fsAutoParts, storeItems)
    fsAutoParts.Close()

let departmentStore = new Form(ClientSize = new Size(865, 390), Text = "Department Store", MaximizeBox = false)

let btnClose = new Button(Left = 17, Top = 17, Text = "Close")
let btnCloseClick e =
    saveInventory()
    departmentStore.Close()
btnClose.Click.Add btnCloseClick
departmentStore.Controls.Add btnClose

[<EntryPoint>]
let main argv =
    Application.Run departmentStore
    0

Either way, you can access the members of the class and use them as you see fit.

As mentioned already, all the members of a list must be of the same type. You can use objects of different classes in a list. The main rule is that the classes must share a common ancestry. In the same way, you can create many lists using a class, or you can create different lists using classes of your choice. In the same way, you can create a list in a list (or a list in a list in a list...) or a list of lists (or a list of lists of lists...).

A List as a Property

We already know that a list can be passed to a function. In the same way, a property of a class can be created from a list. This means that a parameter passed to the constructor of a class can represent a list. You can then use that parameter and assign it to a property. Here are examples:

type EmployeePayroll(emplNbr, name, salary, times) =
    member val EmployeeNumber = emplNbr with get, set
    member val FullName = name with get, set
    member val HourlySalary = salary with get, set
    member val WeeklyTimes = times with get, set

From these properties, the compiler doesn't know the data type of each parameter and a parameter can be anything. This also means that the compiler doesn't know whether a parameter is a list or not. When creating an object from the class, you can pass a list in the placeholder of an argument. Here are examples:

let ep = EmployeePayroll("2080-4813",
			 [ "James"; "Roland"; "Alexanders" ],
			 22.25,
			 [ 8.50; 9.50; 8.00; 10.00; 9.00; 0.00; 0.00 ])

From that object, the compiler can infer the appropriate type of each parameter. You can then use each property as you see fit. In some cases, you must indicate that a property holds a list. In this case, you must create a list as the value of the get-accessor of the property, Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

let mutable yearlyHours = []

type MachineUsageDepreciation(cost, salvage, life, usage) =
    let mutable hrs = usage
    let mutable counter = 0

    member this.HourlyDepreciation with get() = (cost - salvage) / life
    member this.Operations with get() = hrs and set(hours) = hrs <- hours

    member this.DepreciationSchedule with get() = [ for n in hrs do yield (float n, float n * this.HourlyDepreciation) ]

    new(cost, salvage, life) = MachineUsageDepreciation(cost, salvage, life, [])

let unitsOfProduction : Form = new Form(MaximizeBox = false, Text = "Depreciation: Units of Production", ClientSize = new System.Drawing.Size(325, 370), StartPosition = FormStartPosition.CenterScreen)

unitsOfProduction.Controls.Add(new Label(Location = new Point(21, 25), AutoSize = true, Text = "Machine Cost:"))
let txtMachineCost : TextBox = new TextBox(Location = new Point(130, 22), Text = "0.00", TextAlign = HorizontalAlignment.Right, Width = 75)
unitsOfProduction.Controls.Add txtMachineCost

unitsOfProduction.Controls.Add(new Label(Location = new Point(21, 54), AutoSize = true, Text = "Salvage Value:"))
let txtSalvageValue : TextBox = new TextBox(Location = new Point(130, 51), Text = "0.00", TextAlign = HorizontalAlignment.Right, Width = 75)
unitsOfProduction.Controls.Add txtSalvageValue

unitsOfProduction.Controls.Add(new Label(Location = new Point(21, 83), AutoSize = true, Text = "Estimated Life:"))
let txtEstimatedLife : TextBox = new TextBox(Location = new Point(130, 80), Text = "0.00", TextAlign = HorizontalAlignment.Right, Width = 52)
unitsOfProduction.Controls.Add txtEstimatedLife
unitsOfProduction.Controls.Add(new Label(Location = new Point(187, 83), AutoSize = true, Text = "Hours"))

let lblLine = new Label(Location = new Point(21, 105), AutoSize = true)
lblLine.Text <- "Yearly Operations ________________________________"
unitsOfProduction.Controls.Add lblLine

unitsOfProduction.Controls.Add(new Label(Location = new Point(21, 135), AutoSize = true, Text = "Year:"))
let txtYearlyHours : TextBox = new TextBox(Location = new Point(130, 132), Text = "0.00", TextAlign = HorizontalAlignment.Right, Width = 52)
unitsOfProduction.Controls.Add txtYearlyHours
unitsOfProduction.Controls.Add(new Label(Location = new Point(187, 135), AutoSize = true, Text = "Hours/Year"))

unitsOfProduction.Controls.Add(new Label(Location = new Point(21, 83), AutoSize = true, Text = "Estimated Life:"))

unitsOfProduction.Controls.Add(new Label(Location = new Point(21, 209), AutoSize = true, Text = "Hourly Depreciation:"))
let txtHourlyDepreciation : TextBox = new TextBox(Location = new Point(130, 206), Text = "0.00", TextAlign = HorizontalAlignment.Right, Width = 52)
unitsOfProduction.Controls.Add txtHourlyDepreciation
unitsOfProduction.Controls.Add(new Label(Location = new Point(187, 209), AutoSize = true, Text = "/Hour"))

unitsOfProduction.Controls.Add(new Label(Location = new Point(55, 240), AutoSize = true, Text = "Depreciation Schedule:"))

let lvwDepreciationSchedule : ListView = new ListView(Location = new Point(50, 258), Size = new System.Drawing.Size(225, 95), GridLines = true, View = View.Details, FullRowSelect = true)
lvwDepreciationSchedule.Columns.Add("Year", 40) |> ignore
lvwDepreciationSchedule.Columns.Add("Usage", 95, HorizontalAlignment.Center) |> ignore
lvwDepreciationSchedule.Columns.Add("Depreciation", 85, HorizontalAlignment.Right) |> ignore

unitsOfProduction.Controls.Add lvwDepreciationSchedule

let btnAdd : Button = new Button(Location = new Point(255, 130), Width = 50, Text = "Add")
let btnAddClick e =
    yearlyHours <- yearlyHours @ [ int txtYearlyHours.Text ]

    txtYearlyHours.Text <- "0.00"
    txtYearlyHours.Focus() |> ignore

btnAdd.Click.Add btnAddClick
unitsOfProduction.Controls.Add btnAdd

let btnCalculate : Button = new Button(Location = new Point(24, 163), Size = new System.Drawing.Size(280, 31), Text = "Calculate")
let btnCalculateClick e =
    let machineCost   = float txtMachineCost.Text
    let salvageValue  = float txtSalvageValue.Text
    let estimatedLife = float txtEstimatedLife.Text

    let depreciation = new MachineUsageDepreciation(machineCost, salvageValue, estimatedLife, yearlyHours)
    txtHourlyDepreciation.Text <- sprintf "%0.03f" depreciation.HourlyDepreciation

    lvwDepreciationSchedule.Items.Clear()

    let mutable year = 1
    for usage in depreciation.DepreciationSchedule do
        let (hour, depreciation) = usage

        let lviDepreciation = new ListViewItem(sprintf "%i" year)
        lviDepreciation.SubItems.Add(sprintf "%0.0f Hours/Year" hour) |> ignore
        lviDepreciation.SubItems.Add(sprintf "$%0.0f" depreciation) |> ignore
        lvwDepreciationSchedule.Items.Add lviDepreciation |> ignore
        year <- year + 1
btnCalculate.Click.Add btnCalculateClick
unitsOfProduction.Controls.Add btnCalculate

do Application.Run unitsOfProduction

Here is an example of executing the program:

A List as a Property A List as a Property
A List as a Property A List as a Property
A List as a Property A List as a Property
A List as a Property

Characteristics of Lists

 

Introduction

In the F# language, lists are represented by a module named List. The module is defined in the Microsoft.FSharp.Collections namespace. To provide some characteristics to a list, the Microsoft.FSharp.Collections namespace provides a generic union named List. This union is quipped with two constructors (operators) and various propertites.

An Empty List

We saw that, to create an empty list, you can assign empty square brackets [] to it. Alternatively, to let you create an empty list, the List module is equipped with a function named empty. Here is an example of calling it and getting its value:

let noTenant = List.empty

After creating a list and while using it, at any time, to let you find out whether the list has no item, the List union is equipped with a Boolean property named IsEmpty.

The Number of Items in a List

The number of items in a list is referred to as its length. To support it, the List union contains a property named Length.Its equivalent in the List module is the length() function. Its signature is:

List.length : 'T list -> int

As you can see, this function takes a list as argument and it returns an integer as the number of items in the list.

Primary Operations on the Items of Lists

 

Appending a List to a List

As an alternative to the @ operator, the List module provides a function named append. Its signature is:

List.append : 'T list -> 'T list -> 'T list

This function takes two arguments that each is a list. The function returns a list. Here is an example of calling it:

let chefSpecial = [
    "Seafood Delight"
    "General Tso's Chicken"
    "Human Triple Delight"
    "Beef & Scallops"
    "Subgum Wonton" ]

let soups = [
    "Wonton Soup"
    "Chow Fun Noodle Soup"
    "Shredded Chicken Noodle Soup" ]

let food = List.append chefSpecial soups

In the same way, you can append any types of lists, such as lists of tuples. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

let factors15Years = [ (15, 2.000, 6.43); (15, 2.125, 6.49); (15, 2.250, 6.55); (15, 2.375, 6.60); (15, 2.500, 6.66);
                       (15, 2.625, 6.72); (15, 2.750, 6.78); (15, 2.875, 6.84); (15, 3.000, 6.90); (15, 3.125, 6.96);
                       (15, 3.250, 7.02); (15, 3.375, 7.08); (15, 3.500, 7.14); (15, 3.625, 7.21); (15, 3.750, 7.27);
                       (15, 3.875, 7.33); (15, 4.000, 7.39); (15, 4.125, 7.45); (15, 4.250, 7.52); (15, 4.375, 7.58);
                       (15, 4.500, 7.64); (15, 4.625, 7.71); (15, 4.750, 7.77); (15, 4.875, 7.84); (15, 5.000, 7.90);
                       (15, 5.125, 7.97); (15, 5.250, 8.03); (15, 5.375, 8.10); (15, 5.500, 8.18); (15, 5.625, 8.24);
                       (15, 5.750, 8.31); (15, 5.875, 8.37); (15, 6.000, 8.44); (15, 6.125, 8.51); (15, 6.250, 8.58);
                       (15, 6.375, 8.64); (15, 6.500, 8.72); (15, 6.625, 8.78); (15, 6.750, 8.85); (15, 6.875, 8.92);
                       (15, 7.000, 8.99); (15, 7.125, 9.06); (15, 7.250, 9.13); (15, 7.375, 9.20); (15, 7.500, 9.27);
                       (15, 7.625, 9.34); (15, 7.750, 9.41); (15, 7.875, 9.48) ]

let factors30Years = [ (30, 2.000, 3.69); (30, 2.125, 3.75); (30, 2.250, 3.82); (30, 2.375, 3.88);
                       (30, 2.500, 3.95); (30, 2.625, 4.01); (30, 2.750, 4.08); (30, 2.875, 4.14);
                       (30, 3.000, 4.21); (30, 3.125, 4.28); (30, 3.250, 4.35); (30, 3.375, 4.42);
                       (30, 3.500, 4.49); (30, 3.625, 4.56); (30, 3.750, 4.63); (30, 3.875, 4.70);
                       (30, 4.000, 4.77); (30, 4.125, 4.84); (30, 4.250, 4.91); (30, 4.375, 4.99);
                       (30, 4.500, 5.06); (30, 4.625, 5.14); (30, 4.750, 5.21); (30, 4.875, 5.29);
                       (30, 5.000, 5.36); (30, 5.125, 5.44); (30, 5.250, 5.52); (30, 5.375, 5.59);
                       (30, 5.500, 5.68); (30, 5.625, 5.76); (30, 5.750, 5.84); (30, 5.875, 5.92);
                       (30, 6.000, 6.00); (30, 6.125, 6.08); (30, 6.250, 6.16); (30, 6.375, 6.24);
                       (30, 6.500, 6.33); (30, 6.625, 6.40); (30, 6.750, 6.49); (30, 6.875, 6.57);
                       (30, 7.000, 6.65); (30, 7.125, 6.74); (30, 7.250, 6.82); (30, 7.375, 6.91);
                       (30, 7.500, 6.99); (30, 7.625, 7.08); (30, 7.750, 7.16); (30, 7.875, 7.25) ]

let amortizationTable = List.append factors15Years factors30Years

let amortizationFactor : Form = new Form(MaximizeBox = false, Text = "Amortization Table", ClientSize = new Size(275, 445), StartPosition = FormStartPosition.CenterScreen)

amortizationFactor.Controls.Add(new Label(Text = "15-Year Amortization Table", Height = 13, Left = 12, Top = 12, Width = 150))

let lbxAmortizationTable : ListBox = new ListBox(Left = 12, Top = 32, Width = 250, Height = 400)
lbxAmortizationTable.Items.Add("\t\tInterest\tMonthly") |> ignore
lbxAmortizationTable.Items.Add("    \tYear\tRate\tPayment Factor") |> ignore
lbxAmortizationTable.Items.Add("-------------------------------------------------------------------------") |> ignore

for factor in amortizationTable do
    let year, interestRate, paymentFactor = factor
    lbxAmortizationTable.Items.Add(sprintf "\t%i\t%0.03f\t%0.02f" year interestRate paymentFactor) |> ignore

amortizationFactor.Controls.Add lbxAmortizationTable

do Application.Run amortizationFactor

This would produce:

Appending a List to a List Appending a List to a List

Getting an Item Based on Its Index

The items of a list are stored in a zero-based list. The first item occupies the 0 (zero) position. The second item occupied the 1 position, and so on. To let you get to an item based on its position+1 in the list, the List union is equipped with an indexed property named Item. Here is an example of using the property:

let numbers = [3 .. 2 .. 20 ]

let sixth = numbers.Item(5)

In the same way, you can access any member of the list based on its position. Make sure you provide an index that is less than or equal to the number of items - 1. Here is an example:

Getting an Item Based on Its Index

open System
open System.Drawing
open System.Windows.Forms 

type Employee = {
    EmployeeNumber : string
    FirstName      : string
    LastName       : string
    HourlySalary   : float
    Username       : string
    Password       : string }

let employees = [
    { EmployeeNumber = "8022-3840"; FirstName = "Mark";   LastName = "Plant";      HourlySalary = 24.55; Username = "mplant";     Password = "Password1" }
    { EmployeeNumber = "5840-2497"; FirstName = "Carol";  LastName = "Mylans";     HourlySalary = 16.25; Username = "cmylans";    Password = "Password2" }
    { EmployeeNumber = "2081-7008"; FirstName = "Maria";  LastName = "Pappas";     HourlySalary = 22.75; Username = "mpappas";    Password = "Password3" }
    { EmployeeNumber = "9206-8405"; FirstName = "Martin"; LastName = "Perhaps";    HourlySalary = 26.85; Username = "mperhaps";   Password = "Password4" }
    { EmployeeNumber = "2080-4813"; FirstName = "James";  LastName = "Alexanders"; HourlySalary = 22.25; Username = "jalexander"; Password = "Password5" } ]

let loginDialogBox : Form = new Form(MinimizeBox = false,
                              MaximizeBox = false,
                              Text = "Kolo Bank",
                              FormBorderStyle = FormBorderStyle.FixedDialog,
                              ClientSize = new System.Drawing.Size(210, 137),
                              StartPosition = FormStartPosition.CenterScreen)

let lblUsername : Label = new Label(AutoSize = true, 
                                    Text = "Username:",
                                    Location = new Point(21, 24))
loginDialogBox.Controls.Add(lblUsername)

let txtUsername : TextBox = new TextBox(Location = new Point(85, 21),
                                        Size = new System.Drawing.Size(100, 20))
loginDialogBox.Controls.Add(txtUsername)

let lblPassword : Label = new Label(Text = "Password:", AutoSize = true,
                                   Location = new System.Drawing.Point(21, 59))
loginDialogBox.Controls.Add(lblPassword)

let txtPassword : TextBox = new TextBox(PasswordChar = '*',
                                        Location = new Point(85, 56),
                                        Size = new System.Drawing.Size(100, 20))
loginDialogBox.Controls.Add(txtPassword)

let btnOK : Button = new Button(Text = "OK", Location = new Point(24, 95), DialogResult = DialogResult.OK)
loginDialogBox.AcceptButton <- btnOK
loginDialogBox.Controls.Add(btnOK)

let btnCancel : Button = new Button(Text = "Cancel",
                                    Location = new Point(110, 95),
                                    DialogResult = System.Windows.Forms.DialogResult.Cancel)
loginDialogBox.CancelButton <- btnCancel
loginDialogBox.Controls.Add(btnCancel)

let switchboard : Form = new Form(MaximizeBox = false,
                              Text = "Kolo Bank",
                              ClientSize = new System.Drawing.Size(600, 320),
                              StartPosition = FormStartPosition.CenterScreen)
let switchboardLoad(e : EventArgs) =
    let mutable employeeFound = false

    if loginDialogBox.ShowDialog() = DialogResult.OK then
        for i = 0 to employees.Length - 1 do
            if(employees.Item(i).Username = txtUsername.Text) && (employees.Item(i).Password = txtPassword.Text) then
                employeeFound <- true

        if employeeFound = true then
            () // Close the login dialog box and open the main form
        else
            MessageBox.Show "The combination of username and password you provided in not valid." |> ignore
            switchboard.Close()
    else // if the user clicks Cancel or close the Login dialog box, ...
        switchboard.Close() // . . . close the application

switchboard.Load.Add(switchboardLoad)

[<EntryPoint>]
let main argv = 
    Application.Run switchboard
    0

If you provide a number beyond the number of items, you will get an error.

As an alternative to the List.Item property, the List module is equipped with a static member function named nth. Its signature is:

List.nth : 'T list -> int -> 'T

This function takes two arguments: a list variable and the index + 1 of the member you want to access. Here is an example of calling it:

let numbers = [3 .. 2 .. 20 ]

let sixth = List.nth numbers 5

Iterating Through a List

Iterating through a list consists of executing a function on each member of the list. One of the List functions used to perform this operation is called iter. Its signature is:

List.iter : ('T -> unit) -> 'T list -> unit

This function takes two arguments. The first is a function to use on each element. The second argument is a list. Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

type StoreItem = {
    ItemNumber  : int
    Category    : string
    SubCategory : string
    ItemName    : string
    ItemSize    : string
    UnitPrice   : float
    DaysInStore : int
    Status      : string }

type SaleItem(number, category, subcat, name, size, price, days) =
    let nbr = ref number
    let cat = ref category
    let sub = ref subcat
    let nm  = ref name
    let sz  = ref size
    let prc = ref price
    let ds  = ref days
    
    member this.ItemNumber  with get() = !nbr and set(value) = nbr := 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.DaysInStore with get() = !ds  and set(value) = ds  := value
    
    member this.DiscountRate with get() =
                                    if !ds >= 60 then 50.00
                                    elif !ds >= 30 then 35.00
                                    elif !ds >= 15 then 10.00
                                    else 0.00
    member this.DiscountAmount with get() = !prc * this.DiscountRate / 100.00    
    member this.MarkedPrice    with get() = this.UnitPrice - this.DiscountAmount
    new() = SaleItem(0, "", "", "", "", 0.00, 0)
   
let mutable storeItems = new List<StoreItem>()

let departmentStore = new Form(ClientSize = new Size(865, 390), Text = "Department Store", MaximizeBox = false)

let departmentStoreLoad _ =
    let bfStoreItems = new BinaryFormatter()
    let strStoreItemsFile : string =  @"C:\Fun Department Store\StoreItems.sis"

    use fsStoreItems = new FileStream(strStoreItemsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
    storeItems <- bfStoreItems.Deserialize(fsStoreItems) :?> List<StoreItem>
    fsStoreItems.Close()

    let salesItems = [ for item in storeItems do if item.Status = "Flawless" then yield new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize, item.UnitPrice, item.DaysInStore) ]
    departmentStore.Text <- sprintf "%i" salesItems.Length

    let lvwStoreItems : ListView = new ListView(GridLines = true, View = View.Details, FullRowSelect = true, Location = new Point(12, 12), Size = new System.Drawing.Size(840, 330))
    let mutable col = lvwStoreItems.Columns.Add("Item #", 60)
    col <- lvwStoreItems.Columns.Add("Category", 65)
    col <- lvwStoreItems.Columns.Add("Sub-Category", 80)
    col <- lvwStoreItems.Columns.Add("Item Name", 220)
    col <- lvwStoreItems.Columns.Add("Item Size", 60)
    col <- lvwStoreItems.Columns.Add("Unit Price", 60, HorizontalAlignment.Right)
    col <- lvwStoreItems.Columns.Add("Days in Store", 75, HorizontalAlignment.Right)
    col <- lvwStoreItems.Columns.Add("Disc Rate", 65, HorizontalAlignment.Right)
    col <- lvwStoreItems.Columns.Add("Disc Amt", 60, HorizontalAlignment.Right)
    col <- lvwStoreItems.Columns.Add("Marked Price", 75, HorizontalAlignment.Right)

    List.iter (fun (item : SaleItem) ->
        let mutable lviStoreItem = new ListViewItem(sprintf "%i" item.ItemNumber)
        lviStoreItem.SubItems.Add item.Category |> ignore
        lviStoreItem.SubItems.Add item.SubCategory |> ignore
        lviStoreItem.SubItems.Add item.ItemName |> ignore
        lviStoreItem.SubItems.Add item.ItemSize |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.UnitPrice) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%i" item.DaysInStore) |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.0f%c" item.DiscountRate '%') |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.02f" item.DiscountAmount) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.MarkedPrice) |> ignore
        lvwStoreItems.Items.Add lviStoreItem |> ignore) salesItems

    departmentStore.Controls.Add lvwStoreItems(**)

departmentStore.Load.Add departmentStoreLoad

let btnClose = new Button(Left = 780, Top = 355, Text = "Close")
let btnCloseClick e =
    departmentStore.Close()
btnClose.Click.Add btnCloseClick
departmentStore.Controls.Add btnClose

[<EntryPoint>]
let main argv =
    Application.Run departmentStore
    0

This would produce:

Iterating Through a List

Instead of one, you can iterate through two lists at the same time. To support this, the List module provides the iter2() method. Its signature is:

List.iter2 : ('T1 -> 'T2 -> unit) -> 'T1 list -> 'T2 list -> unit

This function takes one function as argument. That internal function takes two lists as arguments. The lists don't have to have the same types of values. For example, one list can contain values of primitive types while the values of the other list are objects of a class. Both lists must have the same number of elements; that is, the same size. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

let factors15Years = [ (15, 2.000, 6.43); (15, 2.125, 6.49); (15, 2.250, 6.55); (15, 2.375, 6.60); (15, 2.500, 6.66);
                       (15, 2.625, 6.72); (15, 2.750, 6.78); (15, 2.875, 6.84); (15, 3.000, 6.90); (15, 3.125, 6.96);
                       (15, 3.250, 7.02); (15, 3.375, 7.08); (15, 3.500, 7.14); (15, 3.625, 7.21); (15, 3.750, 7.27);
                       (15, 3.875, 7.33); (15, 4.000, 7.39); (15, 4.125, 7.45); (15, 4.250, 7.52); (15, 4.375, 7.58);
                       (15, 4.500, 7.64); (15, 4.625, 7.71); (15, 4.750, 7.77); (15, 4.875, 7.84); (15, 5.000, 7.90);
                       (15, 5.125, 7.97); (15, 5.250, 8.03); (15, 5.375, 8.10); (15, 5.500, 8.18); (15, 5.625, 8.24);
                       (15, 5.750, 8.31); (15, 5.875, 8.37); (15, 6.000, 8.44); (15, 6.125, 8.51); (15, 6.250, 8.58);
                       (15, 6.375, 8.64); (15, 6.500, 8.72); (15, 6.625, 8.78); (15, 6.750, 8.85); (15, 6.875, 8.92);
                       (15, 7.000, 8.99); (15, 7.125, 9.06); (15, 7.250, 9.13); (15, 7.375, 9.20); (15, 7.500, 9.27);
                       (15, 7.625, 9.34); (15, 7.750, 9.41); (15, 7.875, 9.48) ]

let factors30Years = [ (30, 2.000, 3.69); (30, 2.125, 3.75); (30, 2.250, 3.82); (30, 2.375, 3.88); (30, 2.500, 3.95);
                       (30, 2.625, 4.01); (30, 2.750, 4.08); (30, 2.875, 4.14); (30, 3.000, 4.21); (30, 3.125, 4.28);
                       (30, 3.250, 4.35); (30, 3.375, 4.42); (30, 3.500, 4.49); (30, 3.625, 4.56); (30, 3.750, 4.63);
                       (30, 3.875, 4.70); (30, 4.000, 4.77); (30, 4.125, 4.84); (30, 4.250, 4.91); (30, 4.375, 4.99);
                       (30, 4.500, 5.06); (30, 4.625, 5.14); (30, 4.750, 5.21); (30, 4.875, 5.29); (30, 5.000, 5.36);
                       (30, 5.125, 5.44); (30, 5.250, 5.52); (30, 5.375, 5.59); (30, 5.500, 5.68); (30, 5.625, 5.76);
                       (30, 5.750, 5.84); (30, 5.875, 5.92); (30, 6.000, 6.00); (30, 6.125, 6.08); (30, 6.250, 6.16);
                       (30, 6.375, 6.24); (30, 6.500, 6.33); (30, 6.625, 6.40); (30, 6.750, 6.49); (30, 6.875, 6.57);
                       (30, 7.000, 6.65); (30, 7.125, 6.74); (30, 7.250, 6.82); (30, 7.375, 6.91); (30, 7.500, 6.99);
                       (30, 7.625, 7.08); (30, 7.750, 7.16); (30, 7.875, 7.25) ]

let mortgageCompany : Form = new Form(MaximizeBox = false, Text = "Mortgage Company - Loan Processing", ClientSize = new System.Drawing.Size(448, 405), StartPosition = FormStartPosition.CenterScreen)

let lvwAmortizationTable : ListView = new ListView(GridLines = true, View = View.Details, FullRowSelect = true, Location = new Point(12, 12), Size = new System.Drawing.Size(425, 375))

let mutable col = lvwAmortizationTable.Columns.Add("Year", 38)
col <- lvwAmortizationTable.Columns.Add("Interrest Rate", 78, HorizontalAlignment.Center)
col <- lvwAmortizationTable.Columns.Add("Payment Factor", 88, HorizontalAlignment.Center)
col <- lvwAmortizationTable.Columns.Add("Year", 35)
col <- lvwAmortizationTable.Columns.Add("Interrest Rate", 78, HorizontalAlignment.Center)
col <- lvwAmortizationTable.Columns.Add("Payment Factor", 88, HorizontalAlignment.Center)

List.iter2 (fun factor15 factor30 ->
    let (year15, interestRate15, paymentFactor15) = factor15
    let (year30, interestRate30, paymentFactor30) = factor30

    let lviAmortization = new ListViewItem(sprintf "%i" year15)
    lviAmortization.SubItems.Add(sprintf "%0.03f" interestRate15) |> ignore
    lviAmortization.SubItems.Add(sprintf "%0.03f" paymentFactor15) |> ignore
    lviAmortization.SubItems.Add(sprintf "%i" year30) |> ignore
    lviAmortization.SubItems.Add(sprintf "%0.03f" interestRate30) |> ignore
    lviAmortization.SubItems.Add(sprintf "%0.03f" paymentFactor30) |> ignore
    lvwAmortizationTable.Items.Add lviAmortization |> ignore) factors15Years factors30Years

mortgageCompany.Controls.Add lvwAmortizationTable

do Application.Run mortgageCompany

This would produce:

Iterating Through Two Lists

Iterating Through Two Lists

If the lists don't have the same number of items, the compiler would throw and ArgumentException exception.

The Head of a List

To let you get the first member of a list, the List union provides a property named Head. Its signature is:

List.head : 'T list -> 'T

Here is an example of using this property:

open System
open System.Drawing
open System.Windows.Forms 

let metroLine = [ "Shady Grove"; "Rockville"; "Twinbrook"; "White Flint"; "Grosvenor-Strathmore";
                  "Medical Center"; "Bethesda"; "Friendship Heights"; "Tenleytown-AU"; "Van Ness-UDC";
                  "Cleveland Park"; "Woodley Park-Zoo/Adams Morgan"; "Dupont Circle"; "Farragut North";
                  "Metro Center"; "Gallery Pl-Chinatown"; "Judiciary Square"; "Union Station";
                  "NoMa-Gallaudet U"; "Rhode Island Ave-Brentwood";  "Brookland-CUA"; "Fort Totten";
                  "Takoma"; "Silver Spring"; "Forest Glen"; "Wheaton"; "Glenmont" ]

let metropolitanTransport = new Form(ClientSize = new Size(315, 50),
                                     MaximizeBox = false, Text = "Metropolitan Area Transportation",
                                     StartPosition = FormStartPosition.CenterScreen)

let lblMessage = new Label(Size = new Size(300, 445), Location = new Point(12, 12))
metropolitanTransport.Controls.Add lblMessage

lblMessage.Text <- sprintf "The Red Line may start at the %s metro station."
		   (metroLine |> List.head)

[<EntryPoint>]
let main argv =
    Application.Run metropolitanTransport
    0

This would produce:

The Head of a List

The List module provides an equivalent function named head. Here is an example of calling it:

let numbers = [3 .. 2 .. 20 ]

let first = numbers.Head

The Tail of a List

To let you get the list without the first item, the List module has a function named tail. Its signature is:

List.tail : 'T list -> 'T list

The tail is the list of items from the argument but without the first element. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

let metroLine = [ "Shady Grove"; "Rockville"; "Twinbrook"; "White Flint"; "Grosvenor-Strathmore";
                  "Medical Center"; "Bethesda"; "Friendship Heights"; "Tenleytown-AU"; "Van Ness-UDC";
                  "Cleveland Park"; "Woodley Park-Zoo/Adams Morgan"; "Dupont Circle"; "Farragut North";
                  "Metro Center"; "Gallery Pl-Chinatown"; "Judiciary Square"; "Union Station";
                  "NoMa-Gallaudet U"; "Rhode Island Ave-Brentwood";  "Brookland-CUA"; "Fort Totten";
                  "Takoma"; "Silver Spring"; "Forest Glen"; "Wheaton"; "Glenmont" ]

let metropolitanTransport = new Form(ClientSize = new Size(305, 400),
                                     MaximizeBox = false, Text = "Metropolitan Area Transportation",
                                     StartPosition = FormStartPosition.CenterScreen)

let lblMessage = new Label(Size = new Size(300, 25), Location = new Point(12, 12))
metropolitanTransport.Controls.Add lblMessage

let lbxStations = new ListBox(Location = new Point(12, 42), Size = new Size(280, 350))
metropolitanTransport.Controls.Add lbxStations

lblMessage.Text <- "After leaving the first station, the remaining stations are:"

metroLine |> List.tail
          |> List.iter (fun station -> lbxStations.Items.Add(sprintf "%s" station) |> ignore)

[<EntryPoint>]
let main argv =
    Application.Run metropolitanTransport
    0

This would produce:

The Tail of a List

The List union provides an equivalent property named Tail. Here is an example of using it:

let numbers = [3 .. 2 .. 20 ]

let freeOne = numbers.Tail

In a repetitive manner, you can keep calling the List.tail function to get rid of the last item continually. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

let metroLine = [ "Shady Grove"; "Rockville"; "Twinbrook"; "White Flint"; "Grosvenor-Strathmore";
                  "Medical Center"; "Bethesda"; "Friendship Heights"; "Tenleytown-AU"; "Van Ness-UDC";
                  "Cleveland Park"; "Woodley Park-Zoo/Adams Morgan"; "Dupont Circle"; "Farragut North";
                  "Metro Center"; "Gallery Pl-Chinatown"; "Judiciary Square"; "Union Station";
                  "NoMa-Gallaudet U"; "Rhode Island Ave-Brentwood";  "Brookland-CUA"; "Fort Totten";
                  "Takoma"; "Silver Spring"; "Forest Glen"; "Wheaton"; "Glenmont" ]

let metropolitanTransport = new Form(ClientSize = new Size(315, 405),
                                     MaximizeBox = false, Text = "Metropolitan Area Transportation",
                                     StartPosition = FormStartPosition.CenterScreen)

metropolitanTransport.Controls.Add(new Label(Size = new Size(320, 25), Location = new Point(12, 12),
                                             Text = "After leaving the first station, the remaining stations are:"))

let lbxStations1 = new ListBox(Location = new Point(12, 38), Size = new Size(290, 100))
metropolitanTransport.Controls.Add lbxStations1

metropolitanTransport.Controls.Add(new Label(Size = new Size(320, 25), Location = new Point(12, 142),
                                             Text = "After leaving the second station, the remaining stations are:"))

let lbxStations2 = new ListBox(Location = new Point(12, 167), Size = new Size(290, 100))
metropolitanTransport.Controls.Add lbxStations2

metropolitanTransport.Controls.Add(new Label(Size = new Size(320, 25), Location = new Point(12, 272),
                                             Text = "After leaving the third station, the remaining stations are:"))

let lbxStations3 = new ListBox(Location = new Point(12, 298), Size = new Size(290, 100))
metropolitanTransport.Controls.Add lbxStations3

metroLine.Tail |> List.iter (fun station -> lbxStations1.Items.Add(sprintf "%s" station) |> ignore)
metroLine.Tail.Tail |> List.iter (fun station -> lbxStations2.Items.Add(sprintf "%s" station) |> ignore)
metroLine.Tail.Tail.Tail |> List.iter (fun station -> lbxStations3.Items.Add(sprintf "%s" station) |> ignore)

[<EntryPoint>]
let main argv =
    Application.Run metropolitanTransport
    0

This would produce:

The Tail of a List

This technique of combining the head (or Head) and the tail (or Tail) function calls can be used in a recursive function to perform an operation on all members of a list.

The Last Member of a List

To get the last member of a list, you can call the List.Item property and pass the Length property - 1. Here is an example:

let numbers = [3 .. 2 .. 20 ]
sprintf "Numbers: %A" numbers

let last = numbers.Item(numbers.Length - 1)

Conditional Operations on Lists

 

Replacing the First Member of a List

The List union is equipped with a static method used to create a new list by specifying the first member of your choice and existing members from another list. The method is called Cons. Its signature is:

static member List.Cons : 'T * 'T list -> 'T list

This method takes two arguments and returns a list. The first argument is the value that will be used as the first in the new list. The second argument is the list to which the first argument will be added. Here is an example of calling it:

open System
open System.Drawing
open System.Windows.Forms 

type FoodItem = {
    ItemCode  : string
    ItemName  : string
    Spicy     : bool
    UnitPrice : float }

let states = [
    { ItemCode = "LS01"; ItemName = "Sweet and Sour Chicken";  Spicy = false; UnitPrice =  5.95 }
    { ItemCode = "CS01"; ItemName = "Honey Walnut";            Spicy = false; UnitPrice = 10.25 }
    { ItemCode = "SP01"; ItemName = "Wonton Soup";             Spicy = false; UnitPrice =  1.85 }
    { ItemCode = "LS02"; ItemName = "Cashew Chicken";          Spicy = false; UnitPrice =  6.25 }
    { ItemCode = "CS04"; ItemName = "Garlic Eggplant Chicken"; Spicy = true;  UnitPrice = 10.95 }
    { ItemCode = "AP01"; ItemName = "Egg Roll";                Spicy = false; UnitPrice =  1.25 }
    { ItemCode = "LS03"; ItemName = "New South Wales";         Spicy = false; UnitPrice =  5.75 }
    { ItemCode = "CS04"; ItemName = "Garlic Eggplant Chicken"; Spicy = true;  UnitPrice = 12.85 }
    { ItemCode = "AP03"; ItemName = "Crab Rangoons (6)";       Spicy = false; UnitPrice =  4.95 }
    { ItemCode = "AP02"; ItemName = "Vegetable Spring Roll";   Spicy = false; UnitPrice =  1.25 }
    { ItemCode = "SP02"; ItemName = "Egg Drop Soup";           Spicy = false; UnitPrice = 1.95 } ]

let totalLand = List.Cons({ ItemCode = "CS03"; ItemName = "Hawaii Chicken";  Spicy = false; UnitPrice =  10.95 }, FoodMenu.Tail)

let australianStates : Form = new Form()
australianStates.MaximizeBox <- false
australianStates.Text <- "Restaurant - Food Menu"
australianStates.ClientSize <- new System.Drawing.Size(345, 203)
australianStates.StartPosition <- FormStartPosition.CenterScreen

let lvwFoodMenu : ListView = new ListView()
lvwFoodMenu.GridLines <- true
lvwFoodMenu.View <- View.Details
lvwFoodMenu.FullRowSelect <- true
lvwFoodMenu.Location <- new Point(12, 12)
lvwFoodMenu.Size <- new System.Drawing.Size(320, 180)
let mutable col = lvwFoodMenu.Columns.Add("Code", 45)
col <- lvwFoodMenu.Columns.Add("Food Item Name", 150)
col <- lvwFoodMenu.Columns.Add("Spicy?", 60, HorizontalAlignment.Center)
col <- lvwFoodMenu.Columns.Add("Unit Price", 60, HorizontalAlignment.Right)

List.iter (fun (food : FoodItem) ->
    let lviFoodItem = new ListViewItem(food.ItemCode)
    lviFoodItem.SubItems.Add food.ItemName |> ignore
    match food.Spicy with
    | true -> lviFoodItem.SubItems.Add "Yes" |> ignore
    | false -> lviFoodItem.SubItems.Add "No" |> ignore
    lviFoodItem.SubItems.Add (sprintf "%0.02f" food.UnitPrice) |> ignore
    lvwFoodMenu.Items.Add lviFoodItem |> ignore) totalLand

australianStates.Controls.Add lvwFoodMenu

[<EntryPoint>]
let main argv =
    Application.Run australianStates
    0

This would produce:

Replacing the First Member of a List

If an Element Exists in a List

To let you find out whether a certain element exists in a list, the List module is equipped with a function named exists. Its signature is:

List.exists : ('T -> bool) -> 'T list -> bool

Here is an example of calling this function:

Getting an Item Based on Its Index

open System
open System.Drawing
open System.Windows.Forms 

type Employee = {
    EmployeeNumber : string
    FirstName      : string
    LastName       : string
    HourlySalary   : float
    Username       : string
    Password       : string }

let employees = [
    { EmployeeNumber = "8022-3840"; FirstName = "Mark";   LastName = "Plant";      HourlySalary = 24.55; Username = "mplant";     Password = "Password1" }
    { EmployeeNumber = "5840-2497"; FirstName = "Carol";  LastName = "Mylans";     HourlySalary = 16.25; Username = "cmylans";    Password = "Password2" }
    { EmployeeNumber = "2081-7008"; FirstName = "Maria";  LastName = "Pappas";     HourlySalary = 22.75; Username = "mpappas";    Password = "Password3" }
    { EmployeeNumber = "9206-8405"; FirstName = "Martin"; LastName = "Perhaps";    HourlySalary = 26.85; Username = "mperhaps";   Password = "Password4" }
    { EmployeeNumber = "2080-4813"; FirstName = "James";  LastName = "Alexanders"; HourlySalary = 22.25; Username = "jalexander"; Password = "Password5" } ]

let loginDialogBox : Form = new Form(MinimizeBox = false,
                              MaximizeBox = false,
                              Text = "Kolo Bank",
                              FormBorderStyle = FormBorderStyle.FixedDialog,
                              ClientSize = new System.Drawing.Size(210, 137),
                              StartPosition = FormStartPosition.CenterScreen)

let lblUsername : Label = new Label(AutoSize = true, 
                                    Text = "Username:",
                                    Location = new Point(21, 24))
loginDialogBox.Controls.Add(lblUsername)

let txtUsername : TextBox = new TextBox(Location = new Point(85, 21),
                                        Size = new System.Drawing.Size(100, 20))
loginDialogBox.Controls.Add(txtUsername)

let lblPassword : Label = new Label(Text = "Password:", AutoSize = true,
                                   Location = new System.Drawing.Point(21, 59))
loginDialogBox.Controls.Add(lblPassword)

let txtPassword : TextBox = new TextBox(PasswordChar = '*',
                                        Location = new Point(85, 56),
                                        Size = new System.Drawing.Size(100, 20))
loginDialogBox.Controls.Add(txtPassword)

let btnOK : Button = new Button(Text = "OK", Location = new Point(24, 95), DialogResult = DialogResult.OK)
loginDialogBox.AcceptButton <- btnOK
loginDialogBox.Controls.Add(btnOK)

let btnCancel : Button = new Button(Text = "Cancel",
                                    Location = new Point(110, 95),
                                    DialogResult = System.Windows.Forms.DialogResult.Cancel)
loginDialogBox.CancelButton <- btnCancel
loginDialogBox.Controls.Add(btnCancel)

let switchboard : Form = new Form(MaximizeBox = false,
                              Text = "Kolo Bank",
                              ClientSize = new System.Drawing.Size(600, 320),
                              StartPosition = FormStartPosition.CenterScreen)
let switchboardLoad(e : EventArgs) =
    let mutable employeeFound = false

    if loginDialogBox.ShowDialog() = DialogResult.OK then
        if String.IsNullOrEmpty txtUsername.Text then
            MessageBox.Show("You must provide a user name.", "Kolo Bank") |> ignore
            switchboard.Close()
        if String.IsNullOrEmpty txtPassword.Text then
            MessageBox.Show("You must provide a password.", "Kolo Bank") |> ignore
            switchboard.Close()
        
        let valid = List.exists (fun (empl : Employee) -> (empl.Username = txtUsername.Text) && (empl.Password = txtPassword.Text) ) employees

        if valid = true then ()
        else
            MessageBox.Show("The combination of username and password you provided in not valid.", "Kolo Bank") |> ignore
            switchboard.Close()
    else // if the user clicks Cancel or close the Login dialog box, ...
        switchboard.Close() // . . . close the application
        
switchboard.Load.Add(switchboardLoad)

[<EntryPoint>]
let main argv = 
    Application.Run switchboard
    0

To let you compare elements from two different lists by their corresponding positions, the List module provides a function named exists2. Its signature is:

List.exists2 : ('T1 -> 'T2 -> bool) -> 'T1 list -> 'T2 list -> bool

Checking Whether a Condition Applies to all Items

To let you apply a certain condition to all elements of a list, the List module is equipped with a function named forAll. Its signature is:

List.forall : ('T -> bool) -> 'T list -> bool

This function takes two arguments. The first arguments is a Boolean function to apply to apply to the second argument. If at list one item of the list responds to the condition in the first argument, the function returns true. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

let atmosphere = new Form(MaximizeBox = false,
                          Text = "Atmosphere Layers",
                          ClientSize = new System.Drawing.Size(360, 145),
                          StartPosition = FormStartPosition.CenterScreen)

let lbxLayers : ListBox = new ListBox(Location = new Point(14, 17),
                                      Size = new System.Drawing.Size(330, 95))
atmosphere.Controls.Add lbxLayers

let layers = [ ("Exosphere",    "Satellites")
               ("Thermosphere", "Absorption of Ultraviolet and X-Ray Radiations")
               (" Mesosphere",   "Slow Down of Meteors")
               ("Stratosphere", "Ozone layer")
               ("Troposphere",  "Clouds, Air, and Weather") ]

let lblMessage = new Label(Location = new Point(12, 120),
                           AutoSize = true, Text = "Message")
atmosphere.Controls.Add lblMessage

let mutable i = 0

lbxLayers.Items.Add "Layer\t\tMade For" |> ignore
lbxLayers.Items.Add "----------------------------------------------------------------------------------------------------------------------" |> ignore

for layer in layers do
    let a, b = layer
    i <- lbxLayers.Items.Add(sprintf "%s\t%s" a b)

let all = layers |> List.forall(fun (name, desc) -> name.EndsWith("sphere"))

if all = true then
    lblMessage.Text <- "All layers' names end with \"sphere\""
else
    lblMessage.Text <- "At least one layer name doesn't end with \"sphere\""

[<EntryPoint>]
let main argv =
    Application.Run atmosphere
    0

This would produce:

Checking Whether a Condition Applies to all Items

Numeric Operations on Elements

 

The Sum of the Numbers of a List

The List module supports various arithmetic operations on a list if the elements on that list are numbers.

To let you get the total of the values of a list, the List module is equipped with a function named sum. It simply takes a list as argument. If the list contains only numbers, the function returns their sum. Here are examples:

open System
open System.Drawing
open System.Windows.Forms

type TimeSheet(salary, mon, tue, wed, thu, fri, sat, sun) =
    let sal = ref salary

    let overtimeSalary = !sal * 1.50
    
    member this.HourlySalary with get() = !sal and set(value) = sal := value

    // Returning a Tuple from a method
    member this.GetMondaySummary() =
        let regTime =
            if mon <= 8.00 then
                mon
            else
                8.00
        let overTime =
            if mon <= 8.00 then 0.00
            else mon - 8.00
        let regPay = regTime * !sal
        let overPay = overTime * overtimeSalary
        (regTime, overTime, regPay, overPay)

    member this.GetTuesdaySummary() =
        let regTime = if tue <= 8.00 then tue else 8.00
        let overTime = if tue <= 8.00 then 0.00 else tue - 8.00
        let regPay = (if tue <= 8.00 then tue else 8.00) * !sal
        let overPay = (if tue <= 8.00 then 0.00 else tue - 8.00) * overtimeSalary
        (regTime, overTime, regPay, overPay)
    member this.GetWednesdaySummary() =
        let regTime = if wed <= 8.00 then wed else 8.00
        let overTime = if wed <= 8.00 then 0.00 else wed - 8.00
        let regPay = (if wed <= 8.00 then wed else 8.00) * !sal
        let overPay = (if wed <= 8.00 then 0.00 else wed - 8.00) * overtimeSalary
        (regTime, overTime, regPay, overPay)
    member this.GetThursdaySummary()  = ((if thu <= 8.00 then thu else 8.00), (if thu <= 8.00 then 0.00 else thu - 8.00), ((if thu <= 8.00 then thu else 8.00) * !sal), ((if thu <= 8.00 then 0.00 else thu - 8.00) * overtimeSalary))
    member this.GetFridaySummary()    = ((if fri <= 8.00 then fri else 8.00), (if fri <= 8.00 then 0.00 else fri - 8.00), ((if fri <= 8.00 then fri else 8.00) * !sal), ((if fri <= 8.00 then 0.00 else fri - 8.00) * overtimeSalary))
    member this.GetSaturdaySummary()  = ((if sat <= 8.00 then sat else 8.00), (if sat <= 8.00 then 0.00 else sat - 8.00), ((if sat <= 8.00 then sat else 8.00) * !sal), ((if sat <= 8.00 then 0.00 else sat - 8.00) * overtimeSalary))
    member this.GetSundaySummary()    = ((if sun <= 8.00 then sun else 8.00), (if sun <= 8.00 then 0.00 else sun - 8.00), ((if sun <= 8.00 then sun else 8.00) * !sal), ((if sun <= 8.00 then 0.00 else sun - 8.00) * overtimeSalary))
                   
// Form: Time Sheet Pay Summary
let timeSheetPaySummary : Form = new Form(ClientSize = new Size(596, 377), MaximizeBox = false, Text = "Time Sheet and Pay Summary", StartPosition = FormStartPosition.CenterScreen)

timeSheetPaySummary.Controls.Add(new Label(Location = new Point(22, 24), AutoSize = true, Text = "Hourly Salary:"))
let txtHourlySalary : TextBox = new TextBox(Location = new Point(102, 21), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
timeSheetPaySummary.Controls.Add txtHourlySalary

timeSheetPaySummary.Controls.Add(new Label(Location = new Point( 99, 57), AutoSize = true, Text = "Monday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(156, 57), AutoSize = true, Text = "Tuesday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(210, 57), AutoSize = true, Text = "Wednesday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(274, 57), AutoSize = true, Text = "Thursday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(329, 57), AutoSize = true, Text = "Friday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(386, 57), AutoSize = true, Text = "Saturday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(441, 57), AutoSize = true, Text = "Sunday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(509, 57), AutoSize = true, Text = "Total"))

let txtTimeSheetMonday    : TextBox = new TextBox(Location = new Point(102, 76), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTimeSheetTuesday   : TextBox = new TextBox(Location = new Point(159, 76), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTimeSheetWednesday : TextBox = new TextBox(Location = new Point(216, 76), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTimeSheetThursday  : TextBox = new TextBox(Location = new Point(273, 76), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTimeSheetFriday    : TextBox = new TextBox(Location = new Point(330, 76), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTimeSheetSaturday  : TextBox = new TextBox(Location = new Point(387, 76), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTimeSheetSunday    : TextBox = new TextBox(Location = new Point(444, 76), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTimeSheetTotal     : TextBox = new TextBox(Location = new Point(512, 76), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
timeSheetPaySummary.Controls.AddRange([| txtTimeSheetMonday; txtTimeSheetTuesday; txtTimeSheetWednesday; txtTimeSheetThursday; txtTimeSheetFriday; txtTimeSheetSaturday; txtTimeSheetSunday; txtTimeSheetTotal |])

let btnSummarize : Button = new Button(Location = new Point(102, 111), Size = new Size(461, 35), Text = "Summarize")
timeSheetPaySummary.Controls.Add btnSummarize

timeSheetPaySummary.Controls.Add(new Label(Location = new Point( 99, 159), AutoSize = true, Text = "Monday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(156, 159), AutoSize = true, Text = "Tuesday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(210, 159), AutoSize = true, Text = "Wednesday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(274, 159), AutoSize = true, Text = "Thursday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(329, 159), AutoSize = true, Text = "Friday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(386, 159), AutoSize = true, Text = "Saturday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(441, 159), AutoSize = true, Text = "Sunday"))
timeSheetPaySummary.Controls.Add(new Label(Location = new Point(509, 159), AutoSize = true, Text = "Total"))

timeSheetPaySummary.Controls.Add(new Label(Location = new Point(23, 185), AutoSize = true, Text = "Regular Time:"))
let txtSummaryMondayRegularTime    : TextBox = new TextBox(Location = new Point(102, 182), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryTuesdayRegularTime   : TextBox = new TextBox(Location = new Point(159, 182), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryWednesdayRegularTime : TextBox = new TextBox(Location = new Point(216, 182), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryThursdayRegularTime  : TextBox = new TextBox(Location = new Point(273, 182), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryFridayRegularTime    : TextBox = new TextBox(Location = new Point(330, 182), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummarySaturdayRegularTime  : TextBox = new TextBox(Location = new Point(387, 182), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummarySundayRegularTime    : TextBox = new TextBox(Location = new Point(444, 182), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryTotalRegularTime     : TextBox = new TextBox(Location = new Point(512, 182), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
timeSheetPaySummary.Controls.AddRange([| txtSummaryMondayRegularTime; txtSummaryTuesdayRegularTime; txtSummaryWednesdayRegularTime; txtSummaryThursdayRegularTime; txtSummaryFridayRegularTime; txtSummarySaturdayRegularTime; txtSummarySundayRegularTime; txtSummaryTotalRegularTime |])

timeSheetPaySummary.Controls.Add(new Label(Location = new Point(23, 211), AutoSize = true, Text = "Overtime:"))
let txtSummaryMondayOvertime    : TextBox = new TextBox(Location = new Point(102, 208), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryTuesdayOvertime   : TextBox = new TextBox(Location = new Point(159, 208), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryWednesdayOvertime : TextBox = new TextBox(Location = new Point(216, 208), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryThursdayOvertime  : TextBox = new TextBox(Location = new Point(273, 208), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryFridayOvertime    : TextBox = new TextBox(Location = new Point(330, 208), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummarySaturdayOvertime  : TextBox = new TextBox(Location = new Point(387, 208), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummarySundayOvertime    : TextBox = new TextBox(Location = new Point(444, 208), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryTotalOvertime     : TextBox = new TextBox(Location = new Point(512, 208), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
timeSheetPaySummary.Controls.AddRange([| txtSummaryMondayOvertime; txtSummaryTuesdayOvertime; txtSummaryWednesdayOvertime; txtSummaryThursdayOvertime; txtSummaryFridayOvertime; txtSummarySaturdayOvertime; txtSummarySundayOvertime; txtSummaryTotalOvertime |])

timeSheetPaySummary.Controls.Add(new Label(Location = new Point(23, 237), AutoSize = true, Text = "Regular Pay:"))
let txtSummaryMondayRegularPay    : TextBox = new TextBox(Location = new Point(102, 234), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryTuesdayRegularPay   : TextBox = new TextBox(Location = new Point(159, 234), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryWednesdayRegularPay : TextBox = new TextBox(Location = new Point(216, 234), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryThursdayRegularPay  : TextBox = new TextBox(Location = new Point(273, 234), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryFridayRegularPay    : TextBox = new TextBox(Location = new Point(330, 234), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummarySaturdayRegularPay  : TextBox = new TextBox(Location = new Point(387, 234), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummarySundayRegularPay    : TextBox = new TextBox(Location = new Point(444, 234), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryTotalRegularPay     : TextBox = new TextBox(Location = new Point(512, 234), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
timeSheetPaySummary.Controls.AddRange([| txtSummaryMondayRegularPay; txtSummaryTuesdayRegularPay; txtSummaryWednesdayRegularPay; txtSummaryThursdayRegularPay; txtSummaryFridayRegularPay; txtSummarySaturdayRegularPay; txtSummarySundayRegularPay; txtSummaryTotalRegularPay |])

timeSheetPaySummary.Controls.Add(new Label(Location = new Point(23, 263), AutoSize = true, Text = "Overtime Pay:"))
let txtSummaryMondayOvertimePay    : TextBox = new TextBox(Location = new Point(102, 260), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryTuesdayOvertimePay   : TextBox = new TextBox(Location = new Point(159, 260), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryWednesdayOvertimePay : TextBox = new TextBox(Location = new Point(216, 260), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryThursdayOvertimePay  : TextBox = new TextBox(Location = new Point(273, 260), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryFridayOvertimePay    : TextBox = new TextBox(Location = new Point(330, 260), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummarySaturdayOvertimePay  : TextBox = new TextBox(Location = new Point(387, 260), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummarySundayOvertimePay    : TextBox = new TextBox(Location = new Point(444, 260), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtSummaryTotalOvertimePay     : TextBox = new TextBox(Location = new Point(512, 260), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
timeSheetPaySummary.Controls.AddRange([| txtSummaryMondayOvertimePay; txtSummaryTuesdayOvertimePay; txtSummaryWednesdayOvertimePay; txtSummaryThursdayOvertimePay; txtSummaryFridayOvertimePay; txtSummarySaturdayOvertimePay; txtSummarySundayOvertimePay; txtSummaryTotalOvertimePay |])

let lblLine : Label = new Label(Location = new Point(23, 285), AutoSize = true, Height = 13)
lblLine.Text <- "------------------------------------------------------------------------------------------------------------------------------------------------"
timeSheetPaySummary.Controls.Add lblLine

timeSheetPaySummary.Controls.Add(new Label(Location = new Point(23, 309), AutoSize = true, Text = "Total Pay:"))
let txtTotalMondayPay     : TextBox = new TextBox(Location = new Point(102, 306), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTotalTuesdayPay    : TextBox = new TextBox(Location = new Point(159, 306), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTotalyWednesdayPay : TextBox = new TextBox(Location = new Point(216, 306), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTotalThursdayPay   : TextBox = new TextBox(Location = new Point(273, 306), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTotalFridayPay     : TextBox = new TextBox(Location = new Point(330, 306), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTotalSaturdayPay   : TextBox = new TextBox(Location = new Point(387, 306), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
let txtTotalSundayPay     : TextBox = new TextBox(Location = new Point(444, 306), Width = 51, Text = "0.00", TextAlign = HorizontalAlignment.Right)
timeSheetPaySummary.Controls.AddRange([| txtTotalMondayPay; txtTotalTuesdayPay; txtTotalyWednesdayPay; txtTotalThursdayPay; txtTotalFridayPay; txtTotalSaturdayPay; txtTotalSundayPay |])

let btnSummarizeClick e =
    let hourlySalary = float txtHourlySalary.Text

    let mon = float txtTimeSheetMonday.Text
    let tue = float txtTimeSheetTuesday.Text
    let wed = float txtTimeSheetWednesday.Text
    let thu = float txtTimeSheetThursday.Text
    let fri = float txtTimeSheetFriday.Text
    let sat = float txtTimeSheetSaturday.Text
    let sun = float txtTimeSheetSunday.Text

    let ts = new TimeSheet(hourlySalary, mon, tue, wed, thu, fri, sat, sun)
    
    let (monRegTime, monOvertime, monRegPay, monOvertiPay) = ts.GetMondaySummary()
    let (tueRegTime, tueOvertime, tueRegPay, tueOvertiPay) = ts.GetTuesdaySummary()
    let (wedRegTime, wedOvertime, wedRegPay, wedOvertiPay) = ts.GetWednesdaySummary()
    let (thuRegTime, thuOvertime, thuRegPay, thuOvertiPay) = ts.GetThursdaySummary()
    let (friRegTime, friOvertime, friRegPay, friOvertiPay) = ts.GetFridaySummary()
    let (satRegTime, satOvertime, satRegPay, satOvertiPay) = ts.GetSaturdaySummary()
    let (sunRegTime, sunOvertime, sunRegPay, sunOvertiPay) = ts.GetSundaySummary()

    let regularTime = [ monRegTime;   tueRegTime;   wedRegTime;   thuRegTime;   friRegTime;   satRegTime;   sunRegTime;   ]
    let overtime    = [ monOvertime;  tueOvertime;  wedOvertime;  thuOvertime;  friOvertime;  satOvertime;  sunOvertime   ]
    let regularPay  = [ monRegPay;    tueRegPay;    wedRegPay;    thuRegPay;    friRegPay;    satRegPay;    sunRegPay     ]
    let overtimePay = [ monOvertiPay; tueOvertiPay; wedOvertiPay; thuOvertiPay; friOvertiPay; satOvertiPay; sunOvertiPay; ]
    let timeWorked  = [ mon; tue; wed; thu; fri; sat; sun ]

    txtTimeSheetTotal.Text <- sprintf "%0.02f" (List.sum timeWorked)

    txtSummaryMondayRegularTime.Text    <- sprintf "%0.02f" monRegTime
    txtSummaryMondayOvertime.Text       <- sprintf "%0.02f" monOvertime
    txtSummaryMondayRegularPay.Text     <- sprintf "%0.02f" monRegPay
    txtSummaryMondayOvertimePay.Text    <- sprintf "%0.02f" monOvertiPay

    txtSummaryTuesdayRegularTime.Text   <- sprintf "%0.02f" tueRegTime
    txtSummaryTuesdayOvertime.Text      <- sprintf "%0.02f" tueOvertime
    txtSummaryTuesdayRegularPay.Text    <- sprintf "%0.02f" tueRegPay
    txtSummaryTuesdayOvertimePay.Text   <- sprintf "%0.02f" tueOvertiPay

    txtSummaryWednesdayRegularTime.Text <- sprintf "%0.02f" wedRegTime
    txtSummaryWednesdayOvertime.Text    <- sprintf "%0.02f" wedOvertime
    txtSummaryWednesdayRegularPay.Text  <- sprintf "%0.02f" wedRegPay
    txtSummaryWednesdayOvertimePay.Text <- sprintf "%0.02f" wedOvertiPay

    txtSummaryThursdayRegularTime.Text  <- sprintf "%0.02f" thuRegTime
    txtSummaryThursdayOvertime.Text     <- sprintf "%0.02f" thuOvertime
    txtSummaryThursdayRegularPay.Text   <- sprintf "%0.02f" thuRegPay
    txtSummaryThursdayOvertimePay.Text  <- sprintf "%0.02f" thuOvertiPay

    txtSummaryFridayRegularTime.Text    <- sprintf "%0.02f" friRegTime
    txtSummaryFridayOvertime.Text       <- sprintf "%0.02f" friOvertime
    txtSummaryFridayRegularPay.Text     <- sprintf "%0.02f" friRegPay
    txtSummaryFridayOvertimePay.Text    <- sprintf "%0.02f" friOvertiPay

    txtSummarySaturdayRegularTime.Text  <- sprintf "%0.02f" satRegTime
    txtSummarySaturdayOvertime.Text     <- sprintf "%0.02f" satOvertime
    txtSummarySaturdayRegularPay.Text   <- sprintf "%0.02f" satRegPay
    txtSummarySaturdayOvertimePay.Text  <- sprintf "%0.02f" satOvertiPay

    txtSummarySundayRegularTime.Text    <- sprintf "%0.02f" sunRegTime
    txtSummarySundayOvertime.Text       <- sprintf "%0.02f" sunOvertime
    txtSummarySundayRegularPay.Text     <- sprintf "%0.02f" sunRegPay
    txtSummarySundayOvertimePay.Text    <- sprintf "%0.02f" sunOvertiPay

    txtSummaryTotalRegularTime.Text     <- sprintf "%0.02f" (List.sum regularTime)
    txtSummaryTotalOvertime.Text        <- sprintf "%0.02f" (List.sum overtime)
    txtSummaryTotalRegularPay.Text      <- sprintf "%0.02f" (List.sum regularPay)
    txtSummaryTotalOvertimePay.Text     <- sprintf "%0.02f" (List.sum overtimePay)

    txtTotalMondayPay.Text     <- sprintf "%0.02f" (List.sum [ monRegPay; monOvertiPay ])
    txtTotalTuesdayPay.Text    <- sprintf "%0.02f" (List.sum [ tueRegPay; tueOvertiPay ])
    txtTotalyWednesdayPay.Text <- sprintf "%0.02f" (List.sum [ wedRegPay; wedOvertiPay ])
    txtTotalThursdayPay.Text   <- sprintf "%0.02f" (List.sum [ thuRegPay; thuOvertiPay ])
    txtTotalFridayPay.Text     <- sprintf "%0.02f" (List.sum [ friRegPay; friOvertiPay ])
    txtTotalSaturdayPay.Text   <- sprintf "%0.02f" (List.sum [ satRegPay; satOvertiPay ])
    txtTotalSundayPay.Text     <- sprintf "%0.02f" (List.sum [ sunRegPay; sunOvertiPay ])

btnSummarize.Click.Add btnSummarizeClick

// Button: Close
let btnClose = new Button(Location = new System.Drawing.Point(512, 304), Width = 51, Text = "Close")
let btnCloseClick e = timeSheetPaySummary.Close()
btnClose.Click.Add btnCloseClick
timeSheetPaySummary.Controls.Add btnClose

do Application.Run timeSheetPaySummary

Here is an example of executing the program:

The Sum of the Number of a List

The Sum of the Number of a List

If the list contains at least one item that is not numeric, the function would produce an error.

The Average Number of a List

The List.average() function is used to calculate the average of numbers. Here is an example:

open System
open System.Drawing
open System.Windows.Forms 

type FoodItem = {
    ItemCode  : string
    Category  : string
    ItemName  : string
    Spicy     : bool
    UnitPrice : float }

let states = [
    { ItemCode = "LS01"; Category = "Lunch Special"; ItemName = "Sweet and Sour Chicken";     Spicy = false; UnitPrice =  5.95 }
    { ItemCode = "CS01"; Category = "Chef Special";  ItemName = "Honey Walnut";               Spicy = false; UnitPrice = 10.25 }
    { ItemCode = "AP02"; Category = "Appetizers";    ItemName = "Vegetable Spring Roll";      Spicy = false; UnitPrice =  1.25 }
    { ItemCode = "SP01"; Category = "Soup";          ItemName = "Wonton Soup";                Spicy = false; UnitPrice =  1.85 }
    { ItemCode = "LS02"; Category = "Lunch Special"; ItemName = "Cashew Chicken";             Spicy = false; UnitPrice =  6.25 }
    { ItemCode = "CS01"; Category = "Chef Special";  ItemName = "Garlic Eggplant Chicken";    Spicy = true;  UnitPrice = 10.95 }
    { ItemCode = "AP01"; Category = "Appetizers";    ItemName = "Egg Roll";                   Spicy = false; UnitPrice =  1.25 }
    { ItemCode = "LS03"; Category = "Lunch Special"; ItemName = "New South Wales";           Spicy = false; UnitPrice =  6.55 }
    { ItemCode = "CS03"; Category = "Chef Special";  ItemName = "Butterfly Shrim w. Bacon";   Spicy = true;  UnitPrice = 12.85 }
    { ItemCode = "AP03"; Category = "Appetizers";    ItemName = "Crab Rangoons (6)";          Spicy = false; UnitPrice =  4.95 }
    { ItemCode = "LS04"; Category = "Lunch Special"; ItemName = "Kung Pao Shrimp w. Peanuts"; Spicy = true;  UnitPrice =  7.25 }
    { ItemCode = "LS05"; Category = "Lunch Special"; ItemName = "General Tso's Chicken";      Spicy = false; UnitPrice =  6.85 }
    { ItemCode = "SP02"; Category = "Soup";          ItemName = "Egg Drop Soup";              Spicy = false; UnitPrice =  1.95 } ]

let lunchSpecialPrices = [ for item in FoodMenu -> item.UnitPrice ]
let avgPrice = List.average lunchSpecialPrices

MessageBox.Show (sprintf "Average Price for Lunch Specials: %0.02f" avgPrice, "Restaurant Menu") |> ignore

This would produce:

Numeric Operations on Elements - Average

Working on an Item of a List

 

Finding an Item in a List

To assist you with finding an item in a list, the List module provides a function named find. Its signature is:

List.find : ('T -> bool) -> 'T list -> 'T

This function takes one argument as a function that performs a comparison on each item of the list. If the function finds an item that responds to the condition, it produces that item. If the item was not found, or no item responds to the condition, the function throws a KeyNotFoundException exception. This exception in defined in the System.Collections.Generic namespace. Here is an example:

open System
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic

type SpeedPackage =
| Basic // = 1
| Ultra  // = 22 // Mbps
| Turbo  // = 15 // 1.5 Gbps

type Customer(acntNbr, name, address, city, cnty, state, zip, speed : SpeedPackage) =
    let nbr = ref acntNbr
    let nm  = ref name
    let adr = ref address
    let ct  = ref city
    let cnt = ref cnty
    let stt = ref state
    let zCode = ref zip
    let sp = ref speed
    
    member this.AccountNumber with get() = !nbr and set(value) = nbr := value
    member this.InternetSpeed with get() = !sp and set(value) = sp := value
    member val CustomerName = nm with get, set
    member val Address = adr with get, set
    member val City = ct with get, set
    member val County = cnt with get, set
    member val State = stt with get, set
    member val ZIPCode = zCode with get, set
    member val IncludesDigitalService = false with get, set
    member val IncludesDVRService = false with get, set
    member val IncludesSportPackage = false with get, set
    member val IncludesForeignPackage = false with get, set
    member val LeasingModem = false with get, set
    member val LeasingRemoteControl = false with get, set

and CableBill(client : Customer) =
    let cust = ref client
    let sp   = ref 0.00

    member this.Client with get() = !cust and set(value) = cust := value
    member this.SportPackage with get() = !sp and set(value) = sp := value
    member this.BasicService = 24.95
    member this.DigitalService with get() =
                                match cust.Value.IncludesDigitalService with
                                | true  -> 6.95
                                | false -> 0.00
    member this.DVRService with get() =
                                match cust.Value.IncludesDVRService with
                                | true  -> 9.95
                                | false -> 0.00
    member this.ModemFee with get() =
                                match cust.Value.LeasingModem with
                                | true  -> 5.00
                                | false -> 0.00
    member this.RemoteControlFee with get() =
                                    match cust.Value.LeasingRemoteControl with
                                    | true  -> 2.25
                                    | false -> 0.00
    member this.ForeignPackage with get() =
                                match cust.Value.IncludesForeignPackage with
                                | true  -> 8.25
                                | false -> 0.00
    member this.FCCFee = 0.08
    member this.CityTax with get() =
                            if   (!cust.Value.ZIPCode >= 20000) && (!cust.Value.ZIPCode <= 20500) then 0.46
                            elif (!cust.Value.ZIPCode >= 20850) && (!cust.Value.ZIPCode <= 20855) then 0.26
                            elif (!cust.Value.ZIPCode >= 20901) && (!cust.Value.ZIPCode <= 20910) then 0.22
                            elif (!cust.Value.ZIPCode >= 20781) && (!cust.Value.ZIPCode <= 20783) then 0.14
                            else 0.01
    member this.CountyTax with get() =
                            match !cust.Value.County with
                            | "Montgomery" -> 0.12
                            | "Howard" -> 0.8
                            | "Frederick" -> 0.09
                            | "Somerset" -> 0.14
                            | "Washington" -> 0.24
                            | _ -> 0.00
    member this.StateTax with get() =
                            match !cust.Value.State with
                            | "DC" -> 0.35
                            | "MD" -> 0.26
                            | "VA" -> 0.12
                            | "WV" -> 0.28
                            | _ -> 0.00
    member this.RegulatoryFee = 1.55

and Internet(client : Customer) =
    let cust = ref client
    
    member this.Client with get() = !cust and set(value) = cust := value
    member this.SpeedPackage with get() =
                                match cust.Value.InternetSpeed with
                                | SpeedPackage.Basic -> 24.95
                                | SpeedPackage.Ultra  -> 45.00
                                | SpeedPackage.Turbo  -> 74.75
        
    member this.BasicService = 34.65
    member this.CityTax with get() =
                            if   (!cust.Value.ZIPCode >= 20901) && (!cust.Value.ZIPCode <= 20910) then 0.15
                            elif (!cust.Value.ZIPCode >= 20781) && (!cust.Value.ZIPCode <= 20783) then 0.18
                            else 0.20
    member this.CountyTax with get() =
                            match !cust.Value.County with
                            | "Montgomery" -> 0.16
                            | "Prince George's" -> 0.22
                            | "Howard" -> 0.8
                            | "Washington" -> 0.09
                            | "Somerset" -> 0.14
                            | _ -> 0.00
    member this.StateTax with get() =
                            match !cust.Value.State with
                            | "DC" -> 0.85
                            | "MD" -> 0.60
                            | "VA" -> 0.44
                            | "WV" -> 0.68
                            | _ -> 0.00

let customers = [ new Customer("940-385294-757", "Brian Wiley", "8046 Stellar Rd", "Silver Spring", "Montgomery", "MD", 20904, Ultra);
                  new Customer("472-838257-284", "Jeannette Meeder", "12042 Rabbitt Rd", "Hyattsville", "Prince George's", "MD", 20782, Basic);
                  new Customer("295-739475-669", "Gerard Rooney", "622 Spurrier Ave", "Washington", "Washington", "DC", 20008, Turbo);
                  new Customer("928-374950-073", "James Millers", "4442 Corentha Drv", "Arlington", "Arlington", "VA", 22201, Basic);
                  new Customer("624-931116-926", "Julie Warden", "5927 Santier Rd", "Ellicott City", "Howard", "MD", 21043, Turbo) ]

customers.[0].IncludesSportPackage <- true
customers.[0].IncludesDigitalService <- true
customers.[1].IncludesDigitalService <- false
customers.[1].IncludesDVRService <- true
customers.[1].IncludesSportPackage <- false
customers.[1].IncludesForeignPackage <- false
customers.[1].LeasingModem <- true
customers.[1].LeasingRemoteControl <- false
customers.[2].IncludesForeignPackage <- true
customers.[2].IncludesDigitalService <- true
customers.[2].LeasingModem <- true
customers.[2].LeasingRemoteControl <- true
customers.[4].IncludesDigitalService <- true
customers.[4].IncludesDVRService <- true
customers.[4].LeasingModem <- true
customers.[4].LeasingRemoteControl <- true

let communicationCompany = new Form(Text = "Communication Company", ClientSize = new Size(570, 550), MaximizeBox = true, StartPosition = FormStartPosition.CenterScreen)

let lblCustomerInformation = new Label(Left = 22, Top = 21, Width = 375)
lblCustomerInformation.Text <- "Customer Information _________________________________________"
communicationCompany.Controls.Add lblCustomerInformation

communicationCompany.Controls.Add(new Label(Left = 22, Top = 52, Width = 80, Text = "Accounte #:"))

let txtAccountNumber = new TextBox(Left = 103, Top = 49, Width = 100)
communicationCompany.Controls.Add txtAccountNumber

let txtCustomerName = new TextBox(Left = 209, Top = 49, Width = 165)
communicationCompany.Controls.Add txtCustomerName

communicationCompany.Controls.Add(new Label(Left = 22, Top = 78, Width = 80, Text = "Addess:"))

let txtAddress = new TextBox(Left = 103, Top = 75, Width = 271)
communicationCompany.Controls.Add txtAddress

let txtCity = new TextBox(Left = 103, Top = 101, Width = 100)
communicationCompany.Controls.Add txtCity

let txtCounty = new TextBox(Left = 208, Top = 101, Width = 165)
communicationCompany.Controls.Add txtCounty

let txtState = new TextBox(Left = 379, Top = 101, Width = 54)
communicationCompany.Controls.Add txtState

let txtZIPCode = new TextBox(Left = 438, Top = 101, Width = 95)
communicationCompany.Controls.Add txtZIPCode

let chkIncludesDigitalService = new CheckBox(Left = 103, Top = 127, Width = 145, Height = 13, Text = "Includes Digital Service")
communicationCompany.Controls.Add chkIncludesDigitalService

let chkIncludesDVRService = new CheckBox(Left = 258, Top = 127, Width = 140, Text = "Includes DVR Service")
communicationCompany.Controls.Add chkIncludesDVRService

let chkIncludesSportPackage = new CheckBox(Left = 398, Top = 127, Width = 145, Text = "Includes Sport Package")
communicationCompany.Controls.Add chkIncludesSportPackage

let chkIncludesForeignPackage = new CheckBox(Left = 103, Top = 150, Width = 155, Text = "Includes Foreign Package")
communicationCompany.Controls.Add chkIncludesForeignPackage

let chkLeasingModem = new CheckBox(Left = 258, Top = 150, Width = 140, Text = "Leasing Modem")
communicationCompany.Controls.Add chkLeasingModem

let chkLeasingRemoteControl = new CheckBox(Left = 398, Top = 150, Width = 145, Text = "Leasing Remote Control")
communicationCompany.Controls.Add chkLeasingRemoteControl

let lblCableRecord = new Label(Left = 21, Top = 176, Width = 540)
lblCableRecord.Text <- "Cable Record __________________________________________________________________________"
communicationCompany.Controls.Add lblCableRecord

communicationCompany.Controls.Add(new Label(Left = 21, Top = 208, Width = 80, Text = "Basic Service:"))

let txtCableBasicService = new TextBox(Left = 102, Top = 205, Width = 75, Text = "24.95")
communicationCompany.Controls.Add txtCableBasicService

communicationCompany.Controls.Add(new Label(Left = 193, Top = 208, Width = 90, Text = "Digital Service:"))

let txtDigitalService = new TextBox(Left = 303, Top = 205, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtDigitalService

communicationCompany.Controls.Add(new Label(Left = 21, Top = 234, Width = 80, Text = "DVR Service:"))

let txtDVRService = new TextBox(Left = 101, Top = 231, Width = 75, Text = "0.00")
communicationCompany.Controls.Add txtDVRService

communicationCompany.Controls.Add(new Label(Left = 193, Top = 234, Width = 95, Text = "Foreign Package:"))

let txtForeignPackage = new TextBox(Left = 303, Top = 231, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtForeignPackage

communicationCompany.Controls.Add(new Label(Left = 384, Top = 234, Width = 70, Text = "City Tax:"))

let txtCableCityTax = new TextBox(Left = 463, Top = 231, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtCableCityTax

communicationCompany.Controls.Add(new Label(Left = 21, Top = 260, Width = 70, Text = "Modem Fee:"))

let txtModemFee = new TextBox(Left = 102, Top = 257, Width = 75, Text = "0.00")
communicationCompany.Controls.Add txtModemFee

communicationCompany.Controls.Add(new Label(Left = 193, Top = 260, Width = 110, Text = "Remote Control Fee:"))

let txtRemoteControlFee = new TextBox(Left = 303, Top = 257, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtRemoteControlFee

communicationCompany.Controls.Add(new Label(Left = 384, Top = 260, Width = 70, Text = "County Tax:"))

let txtCableCountyTax = new TextBox(Left = 463, Top = 257, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtCableCountyTax

communicationCompany.Controls.Add(new Label(Left = 21, Top = 286, Width = 70, Text = "FCC Fee:"))

let txtFCCFee = new TextBox(Left = 102, Top = 283, Width = 75, Text = "0.08")
communicationCompany.Controls.Add txtFCCFee

communicationCompany.Controls.Add(new Label(Left = 193, Top = 286, Width = 110, Text = "Regulatory Fee:"))

let txtRegulatoryFee = new TextBox(Left = 303, Top = 283, Width = 70, Text = "1.55")
communicationCompany.Controls.Add txtRegulatoryFee

communicationCompany.Controls.Add(new Label(Left = 384, Top = 286, Width = 70, Text = "State Tax:"))

let txtCableStateTax = new TextBox(Left = 463, Top = 283, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtCableStateTax

let lblInternetRecord = new Label(Left = 21, Top = 310, Width = 540)
lblInternetRecord.Text <- "Internet Record ________________________________________________________________________"
communicationCompany.Controls.Add lblInternetRecord

communicationCompany.Controls.Add(new Label(Left = 21, Top = 341, Width = 85, Text = "Basic Service:"))

let txtInternetBasicService = new TextBox(Left = 118, Top = 338, Width = 75, Text = "34.95")
communicationCompany.Controls.Add txtInternetBasicService

communicationCompany.Controls.Add(new Label(Left = 200, Top = 341, Width = 90, Text = "Speed Package:"))

let txtSpeedPackage = new TextBox(Left = 303, Top = 338, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtSpeedPackage

communicationCompany.Controls.Add(new Label(Left = 384, Top = 341, Width = 70, Text = "State Tax:"))

let txtInternetCityTax = new TextBox(Left = 463, Top = 338, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtInternetCityTax

communicationCompany.Controls.Add(new Label(Left = 384, Top = 369, Width = 70, Text = "County Tax:"))

let txtInternetCountyTax = new TextBox(Left = 463, Top = 366, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtInternetCountyTax

let btnCalculate = new Button(Left = 208, Top = 379, Width = 130, Height = 34, Text = "Calculate")

communicationCompany.Controls.Add(new Label(Left = 21, Top = 395, Width = 95, Text = "Discount Amount:"))

let txtDiscountAmount = new TextBox(Left = 118, Top = 392, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtDiscountAmount

communicationCompany.Controls.Add(new Label(Left = 384, Top = 395, Width = 70, Text = "State Tax:"))

let txtInternetStateTax = new TextBox(Left = 463, Top = 392, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtInternetStateTax

let lblInvoiceSummary = new Label(Left = 21, Top = 420, Width = 580)
lblInvoiceSummary.Text <- "Invoice Summary ______________________________________________________________________"
communicationCompany.Controls.Add lblInvoiceSummary

communicationCompany.Controls.Add(new Label(Left = 23, Top = 457, Width = 70, Text = "Total Cable:"))

let txtTotalCable = new TextBox(Left = 118, Top = 453, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtTotalCable

communicationCompany.Controls.Add(new Label(Left = 216, Top = 456, Width = 115, Text = "Federal Service Fund:"))

let txtFederalServiceFund = new TextBox(Left = 355, Top = 453, Width = 70, Text = "0.13")
communicationCompany.Controls.Add txtFederalServiceFund

communicationCompany.Controls.Add(new Label(Left = 23, Top = 483, Width = 80, Text = "Total Internet:"))

let txtTotalInternet = new TextBox(Left = 118, Top = 480, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtTotalInternet

communicationCompany.Controls.Add(new Label(Left = 216, Top = 483, Width = 138, Text = "Regulatory Recovery Fee:"))

let txtRegulatoryRecoveryFee = new TextBox(Left = 355, Top = 480, Width = 70, Text = "0.65")
communicationCompany.Controls.Add txtRegulatoryRecoveryFee

communicationCompany.Controls.Add(new Label(Left = 216, Top = 509, Width = 110, Text = "Total Amount Due:"))

let txtTotalAmountDue = new TextBox(Left = 355, Top = 507, Width = 70, Text = "0.00")
communicationCompany.Controls.Add txtTotalAmountDue

let txtAccountNumberLostFocus e =
    if String.IsNullOrEmpty txtAccountNumber.Text then
        txtCustomerName.Text <- ""
        txtAddress.Text <- ""
        txtCity.Text <- ""
        txtCounty.Text <- ""
        txtState.Text <- ""
        txtZIPCode.Text <- ""
        chkIncludesDigitalService.Checked <- false
        chkIncludesDVRService.Checked <- false
        chkIncludesSportPackage.Checked <- false
        chkIncludesForeignPackage.Checked <- false
        chkLeasingModem.Checked <- false
        chkLeasingRemoteControl.Checked <- false
    else
        try
            let accountNumber = txtAccountNumber.Text
            let cust = List.find (fun (client : Customer) -> client.AccountNumber = accountNumber) customers
            let mutable cable = new CableBill(cust)
            let mutable web   = new Internet(cust)

            txtCustomerName.Text <- !cust.CustomerName
            txtAddress.Text      <- !cust.Address
            txtCity.Text         <- !cust.City
            txtCounty.Text       <- !cust.County
            txtState.Text        <- !cust.State
            txtZIPCode.Text      <-sprintf "%i" !cust.ZIPCode

            chkIncludesDigitalService.Checked <- cust.IncludesDigitalService
            chkIncludesDVRService.Checked     <- cust.IncludesDVRService
            chkIncludesSportPackage.Checked   <- cust.IncludesSportPackage
            chkIncludesForeignPackage.Checked <- cust.IncludesSportPackage 
            chkLeasingModem.Checked           <- cust.LeasingModem 
            chkLeasingRemoteControl.Checked   <- cust.LeasingRemoteControl 
    
            txtDigitalService.Text   <- sprintf "%0.02f" cable.DigitalService
            txtDVRService.Text       <- sprintf "%0.02f" cable.DVRService 
            txtForeignPackage.Text   <- sprintf "%0.02f" cable.ForeignPackage 
            txtModemFee.Text         <- sprintf "%0.02f" cable.ModemFee
            txtRemoteControlFee.Text <- sprintf "%0.02f" cable.RemoteControlFee
            txtFCCFee.Text           <- sprintf "%0.02f" cable.FCCFee
            txtCableCityTax.Text     <- sprintf "%0.02f" cable.CityTax
            txtCableCountyTax.Text   <- sprintf "%0.02f" cable.CountyTax 
            txtCableStateTax.Text    <- sprintf "%0.02f" cable.StateTax
    
            txtSpeedPackage.Text      <- sprintf "%0.02f" web.SpeedPackage
            txtInternetCityTax.Text   <- sprintf "%0.02f" web.CityTax
            txtInternetCountyTax.Text <- sprintf "%0.02f" web.CountyTax 
            txtInternetStateTax.Text  <- sprintf "%0.02f" web.StateTax
        with
        | :? KeyNotFoundException as exc ->
                txtCustomerName.Text <- ""
                txtAddress.Text <- ""
                txtCity.Text <- ""
                txtCounty.Text <- ""
                txtState.Text <- ""
                txtZIPCode.Text <- ""
                chkIncludesDigitalService.Checked <- false
                chkIncludesDVRService.Checked <- false
                chkIncludesSportPackage.Checked <- false
                chkIncludesForeignPackage.Checked <- false
                chkLeasingModem.Checked <- false
                chkLeasingRemoteControl.Checked <- false
    
                txtDigitalService.Text    <- ""
                txtDVRService.Text        <- ""
                txtForeignPackage.Text    <- ""
                txtModemFee.Text          <- ""
                txtRemoteControlFee.Text  <- ""
                txtFCCFee.Text            <- ""
                txtCableCityTax.Text      <- ""
                txtCableCountyTax.Text    <- ""
                txtCableStateTax.Text     <- ""
    
                txtSpeedPackage.Text      <- ""
                txtInternetCityTax.Text   <- ""
                txtInternetCountyTax.Text <- ""
                txtInternetStateTax.Text  <- ""

txtAccountNumber.Leave.Add txtAccountNumberLostFocus

let btnCalculateClick e =
    let cableBasicService = float txtCableBasicService.Text
    let digitalService = float txtDigitalService.Text
    let dvrService = float txtDVRService.Text
    let foreignPackage = float txtForeignPackage.Text
    let modemFee = float txtModemFee.Text
    let remoteControlFee = float txtRemoteControlFee.Text
    let fccFee = float txtFCCFee.Text
    let regulatoryFee = float txtRegulatoryFee.Text
    let cableCityTax = float txtCableCityTax.Text
    let cableCountyTax = float txtCableCountyTax.Text
    let cableStateTax = float txtCableStateTax.Text

    let internetBasicService = float txtInternetBasicService.Text
    let speedPackage = float txtSpeedPackage.Text
    let InternetCityTax = float txtInternetCityTax.Text
    let InternetCountyTax = float txtInternetCountyTax.Text
    let InternetStateTax = float txtInternetStateTax.Text

    let federalServiceFund = float txtFederalServiceFund.Text
    let regulatoryRecoveryFee = float txtRegulatoryRecoveryFee.Text
    let discountAmount = float txtDiscountAmount.Text

    let totalCable = cableBasicService + digitalService + dvrService + foreignPackage + modemFee + remoteControlFee + fccFee + regulatoryFee + cableCityTax + cableCountyTax + cableStateTax
    let totalInternet = internetBasicService + speedPackage + InternetCityTax + InternetCountyTax + InternetStateTax
    let totalAmountDue = totalCable + totalInternet + federalServiceFund + regulatoryRecoveryFee - discountAmount
        
    txtTotalCable.Text <- sprintf "%0.02f" totalCable
    txtTotalInternet.Text <- sprintf "%0.02f" totalInternet
    txtTotalAmountDue.Text <- sprintf "%0.02f" totalAmountDue

btnCalculate.Click.Add btnCalculateClick
communicationCompany.Controls.Add btnCalculate

let btnClose = new Button(Left = 458, Top  = 504, Text = "Close")
let btnCloseClick e = communicationCompany.Close()
btnClose.Click.Add btnCloseClick
communicationCompany.Controls.Add btnClose

do Application.Run communicationCompany

This would produce:

Finding an Item in a List

Finding an Item in a List

Finding an Item in a List

If the list is made of tuples, you can look for a certain tuple that would be a combination of values of your choice. If that tuple exists in the list, the function would return it. After getting that list, to access each member of the tuple, you can declare some variables that each represent the values in the tuple. Here is an example:

open System
open System.Windows.Forms

let calculateDownPaymentAmount value rate = value * rate / 100.00

// Form: Loan Evaluation
let loanEvaluation = new Form(Width = 260, Height = 310, MaximizeBox = false, Text = "Loan Evaluation")

// Loan Amount
let lblHouseValue = new Label(Left = 22, Top = 20, Width = 128, Text = "House Value:")
loanEvaluation.Controls.Add lblHouseValue
let txtHouseValue : TextBox = new TextBox(Left = 156, Top = 17, Width = 75, Text = "0.00", TextAlign = HorizontalAlignment.Right)
loanEvaluation.Controls.Add txtHouseValue

// Down Payment Rate
loanEvaluation.Controls.Add(new Label(Left = lblHouseValue.Left, Top = 46, Width = lblHouseValue.Width, Text = "Down Payment Rate:"))
let txtDownPaymentRate : TextBox = new TextBox(Left = txtHouseValue.Left, Top = 43, Width = 50, Text = "0.00", TextAlign = HorizontalAlignment.Right)
loanEvaluation.Controls.Add txtDownPaymentRate
loanEvaluation.Controls.Add(new Label(Left = 212, Top = 46, Width = 20, Text = "%"))

// Annual Interest Rate
loanEvaluation.Controls.Add(new Label(Left = lblHouseValue.Left, Top = 72, Width = lblHouseValue.Width, Text = "Annual Interest Rate:"))
let txtAnnualInterestRate : TextBox = new TextBox(Left = txtHouseValue.Left, Top = 69, Width = txtDownPaymentRate.Width, Text = "0.00", TextAlign = HorizontalAlignment.Right)
loanEvaluation.Controls.Add txtAnnualInterestRate
loanEvaluation.Controls.Add(new Label(Left = 212, Top = 72, Width = 20, Text = "%"))

// Periods
loanEvaluation.Controls.Add(new Label(Left = lblHouseValue.Left, Top = 102, Width = lblHouseValue.Width, Text = "Periods:"))
let txtPeriods : TextBox = new TextBox(Left = txtHouseValue.Left, Top = 95, Width = 40, Text = "30", TextAlign = HorizontalAlignment.Right)
loanEvaluation.Controls.Add txtPeriods
loanEvaluation.Controls.Add(new Label(Left = 199, Top = 98, Width = 40, Text = "Years"))

// Button: Evaluate
let btnEvaluate = new Button(Left = txtHouseValue.Left, Top = 124, Width = 75, Height = 27, Text = "Evaluate")

// Down Payment Amount
loanEvaluation.Controls.Add(new Label(Left = lblHouseValue.Left, Top = 162, Width = lblHouseValue.Width, Text = "Down Payment Amount:"))
let txtDownPaymentAmount : TextBox = new TextBox(Left = txtHouseValue.Left, Top = 159, Width = txtHouseValue.Width, Text = "0.00", TextAlign = HorizontalAlignment.Right)
loanEvaluation.Controls.Add txtDownPaymentAmount

// Amount to Finance
loanEvaluation.Controls.Add(new Label(Left = lblHouseValue.Left, Top = 188, Width = lblHouseValue.Width, Text = "Amount to Finance:"))
let txtFinancingAmount : TextBox = new TextBox(Left = txtHouseValue.Left, Top = 185, Width = txtHouseValue.Width, Text = "0.00", TextAlign = HorizontalAlignment.Right)
loanEvaluation.Controls.Add txtFinancingAmount

// Amount to Finance
loanEvaluation.Controls.Add(new Label(Left = lblHouseValue.Left, Top = 214, Width = lblHouseValue.Width, Text = "Monthly Payment:"))
let txtMonthlyPayment : TextBox = new TextBox(Left = txtHouseValue.Left, Top = 211, Width = txtHouseValue.Width, Text = "0.00", TextAlign = HorizontalAlignment.Right)
loanEvaluation.Controls.Add txtMonthlyPayment

let btnEvaluateClick e =
    let loanAmount = float txtHouseValue.Text
    let downPaymentRate = float txtDownPaymentRate.Text
    let annualInterestRate = float txtAnnualInterestRate.Text

    if annualInterestRate % 0.125 = 0.00 then
        let downPayment = calculateDownPaymentAmount loanAmount downPaymentRate
        let principal = loanAmount - downPayment

        let per1000 = principal / 1000.00
        let mortgagePaymentTable = [ (30, 2.000, 3.69); (30, 2.125, 3.75);
                                     (30, 2.250, 3.82); (30, 2.375, 3.88);
                                     (30, 2.500, 3.95); (30, 2.625, 4.01);
                                     (30, 2.750, 4.08); (30, 2.875, 4.14);
                                     (30, 3.000, 4.21); (30, 3.125, 4.28);
                                     (30, 3.250, 4.35); (30, 3.375, 4.42);
                                     (30, 3.500, 4.49); (30, 3.625, 4.56);
                                     (30, 3.750, 4.63); (30, 3.875, 4.70);
                                     (30, 4.000, 4.77); (30, 4.125, 4.84);
                                     (30, 4.250, 4.91); (30, 4.375, 4.99);
                                     (30, 4.500, 5.06); (30, 4.625, 5.14);
                                     (30, 4.750, 5.21); (30, 4.875, 5.29);
                                     (30, 5.000, 5.36); (30, 5.125, 5.44);
                                     (30, 5.250, 5.52); (30, 5.375, 5.59);
                                     (30, 5.500, 5.68); (30, 5.625, 5.76);
                                     (30, 5.750, 5.84); (30, 5.875, 5.92);
                                     (30, 6.000, 6.00); (30, 6.125, 6.08);
                                     (30, 6.250, 6.16); (30, 6.375, 6.24);
                                     (30, 6.500, 6.33); (30, 6.625, 6.40);
                                     (30, 6.750, 6.49); (30, 6.875, 6.57);
                                     (30, 7.000, 6.65); (30, 7.125, 6.74);
                                     (30, 7.250, 6.82); (30, 7.375, 6.91);
                                     (30, 7.500, 6.99); (30, 7.625, 7.08);
                                     (30, 7.750, 7.16); (30, 7.875, 7.25) ]
                               
        let factor = List.find(fun (a, b, c) -> b = annualInterestRate) mortgagePaymentTable

        let term, interestRate, paymentFactor = factor
        let monthlyPayment = (principal / 1000.00) * paymentFactor

        let strDownPayment    = sprintf "%0.02f" downPayment
        let strFinanceAmount  = sprintf "%0.02f" principal
        let strMonthlyPayment = sprintf "%0.02f" monthlyPayment

        txtDownPaymentAmount.Text <- strDownPayment
        txtFinancingAmount.Text  <- strFinanceAmount
        txtMonthlyPayment.Text   <- strMonthlyPayment
    else
        MessageBox.Show("You must specify an interest rate that is increment of 0.125", "Altair Realtors", MessageBoxButtons.OK, MessageBoxIcon.Exclamation) |> ignore

btnEvaluate.Click.Add btnEvaluateClick
loanEvaluation.Controls.Add btnEvaluate

// Button: Close
let btnClose = new Button(Left = txtHouseValue.Left, Top = 246, Text = "Close")
let btnCloseClick _ =  loanEvaluation.Close()
btnClose.Click.Add btnCloseClick
loanEvaluation.Controls.Add btnClose

do Application.Run loanEvaluation

Here is an example of executing the program:

Finding an Item in a List Finding an Item in a List

Picking an Item from a List

Picking an item from a list consists of applying a condition to its elements from the first to the last but selecting only the first item that responds to that condition. To support this operation, the List module is equipped with a function named pick. Its signature is:

List.pick : ('T -> 'U option) -> 'T list -> 'U

This function takes two arguments. The first argument requires a matching condition and the second is the list that holds the items. Here is an example:

open System
open System.Windows.Forms 

type FoodItem = {
    ItemCode  : string
    Category  : string
    ItemName  : string
    Spicy     : bool
    UnitPrice : float }

let states = [
    { ItemCode = "LS01"; Category = "Lunch Special"; ItemName = "Sweet and Sour Chicken";     Spicy = false; UnitPrice =  5.95 }
    { ItemCode = "CS01"; Category = "Chef Special";  ItemName = "Honey Walnut";               Spicy = false; UnitPrice = 10.25 }
    { ItemCode = "AP02"; Category = "Appetizers";    ItemName = "Vegetable Spring Roll";      Spicy = false; UnitPrice =  1.25 }
    { ItemCode = "SP01"; Category = "Soup";          ItemName = "Wonton Soup";                Spicy = false; UnitPrice =  1.85 }
    { ItemCode = "LS02"; Category = "Lunch Special"; ItemName = "Cashew Chicken";             Spicy = false; UnitPrice =  6.25 }
    { ItemCode = "CS01"; Category = "Chef Special";  ItemName = "Garlic Eggplant Chicken";    Spicy = true;  UnitPrice = 10.95 }
    { ItemCode = "AP01"; Category = "Appetizers";    ItemName = "Egg Roll";                   Spicy = false; UnitPrice =  1.25 }
    { ItemCode = "LS03"; Category = "Lunch Special"; ItemName = "New South Wales";           Spicy = false; UnitPrice =  6.55 }
    { ItemCode = "CS03"; Category = "Chef Special";  ItemName = "Butterfly Shrim w. Bacon";   Spicy = true;  UnitPrice = 12.85 }
    { ItemCode = "AP03"; Category = "Appetizers";    ItemName = "Crab Rangoons (6)";          Spicy = false; UnitPrice =  4.95 }
    { ItemCode = "LS04"; Category = "Lunch Special"; ItemName = "Kung Pao Shrimp w. Peanuts"; Spicy = true;  UnitPrice =  7.25 }
    { ItemCode = "LS05"; Category = "Lunch Special"; ItemName = "General Tso's Chicken";      Spicy = false; UnitPrice =  6.85 }
    { ItemCode = "SP02"; Category = "Soup";          ItemName = "Egg Drop Soup";              Spicy = false; UnitPrice =  1.95 } ]

let firstChefSpecial = List.pick (fun (item : FoodItem) -> match item.Category with | "Chef Special" -> Some(item) | _ -> None) FoodMenu

let result = sprintf "Restaurant Menu - Food Item\n------------------------------\nItem Code:  %s\nCategory:   %s\nItem Name:  %s\nSpicy?      %A\nUnit Price: %0.02f\n=============================="
                     firstChefSpecial.ItemCode firstChefSpecial.Category firstChefSpecial.ItemName firstChefSpecial.Spicy firstChefSpecial.UnitPrice

MessageBox.Show (sprintf "%s" result, "Restaurant Menu") |> ignore

This would produce:

Picking an Item from a List

Sub-Lists

 

Filtering by Some Items

A sub-list is a list derived from an existing list. To create a sub-list, you have many options.

To select some items from a list, you can apply a criterion that would filter some items, put them in a new list, and return that list. To support this operation, the List module provides a function named filter. Its signature is:

List.filter : ('T -> bool) -> 'T list -> 'T list

This function takes a function and a list as arguments. The internal function applies a condition to each item of the list. Every item that responds to the condition is put in a new list. At the end, that list is returned from the List.filter() function. Here is an example:

open System
open System.Windows.Forms

type OccupancyStatus =
| Other
| Available
| Occupied
| NeedsRepair

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

let apartments = [
    { UnitNumber = "101"; Bedrooms = 2; Bathrooms = 2.00; SecurityDeposit = 650; MonthlyRate = 1150; Status = OccupancyStatus.Available   }
    { UnitNumber = "102"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950; Status = OccupancyStatus.NeedsRepair }
    { UnitNumber = "103"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  925; Status = OccupancyStatus.Available   }
    { UnitNumber = "104"; Bedrooms = 3; Bathrooms = 2.00; SecurityDeposit = 850; MonthlyRate = 1350; Status = OccupancyStatus.Occupied    }
    { UnitNumber = "105"; Bedrooms = 2; Bathrooms = 1.00; SecurityDeposit = 550; MonthlyRate = 1150; Status = OccupancyStatus.Available   }
    { UnitNumber = "106"; Bedrooms = 3; Bathrooms = 1.50; SecurityDeposit = 950; MonthlyRate = 1425; Status = OccupancyStatus.NeedsRepair }
    { UnitNumber = "107"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950; Status = OccupancyStatus.Occupied    }
    { UnitNumber = "108"; Bedrooms = 2; Bathrooms = 1.00; SecurityDeposit = 550; MonthlyRate = 1150; Status = OccupancyStatus.Occupied    }
 ]

let ready = List.filter (fun (apart : Apartment) -> apart.Status = OccupancyStatus.Available) apartments

let apartmentsListing = new Form(Width = 335, Height = 150, Text = "Apartments Listing", MaximizeBox = false)

let lbxApartments = new ListBox(Left = 12, Top = 12, Width = 300, Height = 98)

lbxApartments.Items.Add("\t\t\tMonthly") |> ignore
lbxApartments.Items.Add("Unit # Beds\tBaths\tRent\tDeposit\tStatus") |> ignore
lbxApartments.Items.Add("----------------------------------------------------------------------------------------------") |> ignore
for apart in ready do
    lbxApartments.Items.Add(sprintf "%s\t%i\t%.2f\t%d\t%d\t%A" apart.UnitNumber apart.Bedrooms apart.Bathrooms apart.MonthlyRate apart.SecurityDeposit apart.Status) |> ignore
lbxApartments.Items.Add("----------------------------------------------------------------------------------------------") |> ignore

apartmentsListing.Controls.Add lbxApartments

do Application.Run apartmentsListing

This would produce:

Filtering by Some Items

Choosing a Sub-List

To create a sub-list, you can apply a condition to the items of an existing list so that the elements that respond to the condition would be selected and added to the new list. To assist you with this, the List module is equipped with a function named choose. Its signature is:

List.choose : ('T -> 'U option) -> 'T list -> 'U list

This function takes two arguments. The first is a function that includes a matching conditional statement. The second argument is the list on which the condition will be applied. Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

type StoreItem = {
    ItemNumber  : int
    Category    : string
    SubCategory : string
    ItemName    : string
    ItemSize    : string
    UnitPrice   : float
    DaysInStore : int
    Status      : string }

type SaleItem(number, category, subcat, name, size, price, days) =
    let nbr = ref number
    let cat = ref category
    let sub = ref subcat
    let nm  = ref name
    let sz  = ref size
    let prc = ref price
    let ds  = ref days
    
    member this.ItemNumber  with get() = !nbr and set(value) = nbr := 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.DaysInStore with get() = !ds  and set(value) = ds  := value
    
    member this.DiscountRate with get() =
                                    if !ds >= 60 then 50.00
                                    elif !ds >= 30 then 35.00
                                    elif !ds >= 15 then 10.00
                                    else 0.00
    member this.DiscountAmount with get() = !prc * this.DiscountRate / 100.00    
    member this.MarkedPrice    with get() = this.UnitPrice - this.DiscountAmount
    new() = SaleItem(0, "", "", "", "", 0.00, 0)
   
let mutable storeItems = new List<StoreItem>()
let mutable salesItems = []

let departmentStore = new Form(ClientSize = new Size(865, 390),
                               MaximizeBox = false, Text = "Department Store", 
                               StartPosition = FormStartPosition.CenterScreen)

let lvwStoreItems : ListView = new ListView(GridLines = true, View = View.Details, FullRowSelect = true, Location = new Point(12, 12), Size = new System.Drawing.Size(840, 330))
let mutable col = lvwStoreItems.Columns.Add("Item #", 60)
col <- lvwStoreItems.Columns.Add("Category", 65)
col <- lvwStoreItems.Columns.Add("Sub-Category", 80)
col <- lvwStoreItems.Columns.Add("Item Name", 220)
col <- lvwStoreItems.Columns.Add("Item Size", 60)
col <- lvwStoreItems.Columns.Add("Unit Price", 60, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Days in Store", 75, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Disc Rate", 65, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Disc Amt", 60, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Marked Price", 75, HorizontalAlignment.Right)

departmentStore.Controls.Add lvwStoreItems(**)

let showStoreItems items =
    lvwStoreItems.Items.Clear()

    List.iter (fun (item : SaleItem) ->
        let mutable lviStoreItem = new ListViewItem(sprintf "%i" item.ItemNumber)
        lviStoreItem.SubItems.Add item.Category |> ignore
        lviStoreItem.SubItems.Add item.SubCategory |> ignore
        lviStoreItem.SubItems.Add item.ItemName |> ignore
        lviStoreItem.SubItems.Add item.ItemSize |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.UnitPrice) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%i" item.DaysInStore) |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.0f%c" item.DiscountRate '%') |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.02f" item.DiscountAmount) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.MarkedPrice) |> ignore
        lvwStoreItems.Items.Add lviStoreItem |> ignore) items

let departmentStoreLoad _ =
    let bfStoreItems = new BinaryFormatter()
    let strStoreItemsFile : string =  @"C:\Fun Department Store\StoreItems.sis"

    use fsStoreItems = new FileStream(strStoreItemsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
    storeItems <- bfStoreItems.Deserialize(fsStoreItems) :?> List<StoreItem>
    fsStoreItems.Close()

    salesItems <- [ for item in storeItems do if item.Status = "Flawless" then yield new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize, item.UnitPrice, item.DaysInStore) ]
    showStoreItems salesItems
departmentStore.Load.Add departmentStoreLoad

let btnShowAllItems = new Button(Location = new Point(12, 355), Width = 150, Text = "Show all Items")
departmentStore.Controls.Add btnShowAllItems

let btnShowAllItemsClick _ = 
    showStoreItems salesItems
btnShowAllItems.Click.Add btnShowAllItemsClick

let btnWomenItems = new Button(Location = new Point(180, 355), Width = 150, Text = "Show Women Items")
departmentStore.Controls.Add btnWomenItems

let btnWomenItemsClick _ =
    let womenItems = List.choose (fun (item : SaleItem) ->
            match item.Category with
            | "Women" -> Some(item)
            | _ -> None) salesItems
    showStoreItems womenItems
btnWomenItems.Click.Add btnWomenItemsClick

let btnClose = new Button(Left = 780, Top = 355, Text = "Close")
let btnCloseClick e =
    departmentStore.Close()
btnClose.Click.Add btnCloseClick
departmentStore.Controls.Add btnClose

[<EntryPoint>]
let main argv =
    Application.Run departmentStore
    0

This would produce:

Choosing a Sub-List

Choosing a Sub-List

An alternative is to apply the list to a call to this function. Here is an example:

let btnWomenItemsClick _ =
    let womenItems = salesItems |> List.choose (fun (item : SaleItem) ->
            match item.Category with
            | "Women" -> Some(item)
            | _ -> None)
    showStoreItems womenItems

Mapping Some Elements

Mapping elements consists of performing an operation on each element of a list to get a new list that contains the same number of items but each item has the value gotten from the operation. To let you perform this operation, the List module provides a function named map. Its signature is:

List.map : ('T -> 'U) -> 'T list -> 'U list

Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

type StoreItem = {
    ItemNumber  : int
    Category    : string
    SubCategory : string
    ItemName    : string
    ItemSize    : string
    UnitPrice   : float
    DaysInStore : int
    Status      : string }

type SaleItem(number, category, subcat, name, size, price, days) =
    let nbr = ref number
    let cat = ref category
    let sub = ref subcat
    let nm  = ref name
    let sz  = ref size
    let prc = ref price
    let ds  = ref days
    
    member this.ItemNumber  with get() = !nbr and set(value) = nbr := 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.DaysInStore with get() = !ds  and set(value) = ds  := value
    
    member this.DiscountRate with get() =
                                    if !ds >= 60 then 50.00
                                    elif !ds >= 30 then 35.00
                                    elif !ds >= 15 then 10.00
                                    else 0.00
    member this.DiscountAmount with get() = !prc * this.DiscountRate / 100.00    
    member this.MarkedPrice    with get() = this.UnitPrice - this.DiscountAmount
    new() = SaleItem(0, "", "", "", "", 0.00, 0)
   
let mutable storeItems = new List<StoreItem>()
let mutable salesItems = []

let departmentStore = new Form(ClientSize = new Size(865, 390),
                               MaximizeBox = false, Text = "Department Store", 
                               StartPosition = FormStartPosition.CenterScreen)

let lvwStoreItems : ListView = new ListView(GridLines = true, View = View.Details, FullRowSelect = true, Location = new Point(12, 12), Size = new System.Drawing.Size(840, 330))
let mutable col = lvwStoreItems.Columns.Add("Item #", 60)
col <- lvwStoreItems.Columns.Add("Category", 65)
col <- lvwStoreItems.Columns.Add("Sub-Category", 80)
col <- lvwStoreItems.Columns.Add("Item Name", 220)
col <- lvwStoreItems.Columns.Add("Item Size", 60)
col <- lvwStoreItems.Columns.Add("Unit Price", 60, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Days in Store", 75, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Disc Rate", 65, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Disc Amt", 60, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Marked Price", 75, HorizontalAlignment.Right)

departmentStore.Controls.Add lvwStoreItems(**)

let showStoreItems items =
    lvwStoreItems.Items.Clear()

    List.iter (fun (item : SaleItem) ->
        let mutable lviStoreItem = new ListViewItem(sprintf "%i" item.ItemNumber)
        lviStoreItem.SubItems.Add item.Category |> ignore
        lviStoreItem.SubItems.Add item.SubCategory |> ignore
        lviStoreItem.SubItems.Add item.ItemName |> ignore
        lviStoreItem.SubItems.Add item.ItemSize |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.UnitPrice) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%i" item.DaysInStore) |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.0f%c" item.DiscountRate '%') |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.02f" item.DiscountAmount) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.MarkedPrice) |> ignore
        lvwStoreItems.Items.Add lviStoreItem |> ignore) items

let departmentStoreLoad _ =
    let bfStoreItems = new BinaryFormatter()
    let strStoreItemsFile : string =  @"C:\Fun Department Store\StoreItems.sis"

    use fsStoreItems = new FileStream(strStoreItemsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
    storeItems <- bfStoreItems.Deserialize(fsStoreItems) :?> List<StoreItem>
    fsStoreItems.Close()

    salesItems <- [ for item in storeItems do if item.Status = "Flawless" then yield new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize, item.UnitPrice, item.DaysInStore) ]
    showStoreItems salesItems
departmentStore.Load.Add departmentStoreLoad

let btnShowAllItems = new Button(Location = new Point(12, 355), Width = 150, Text = "Show all Items")
departmentStore.Controls.Add btnShowAllItems

let btnShowAllItemsClick _ = 
    showStoreItems salesItems
btnShowAllItems.Click.Add btnShowAllItemsClick

let btnWomenItems = new Button(Location = new Point(170, 355), Width = 150, Text = "Show Women Items")
departmentStore.Controls.Add btnWomenItems

let btnWomenItemsClick _ =
    let womenItems = salesItems |> List.choose (fun (item : SaleItem) ->
            match item.Category with
            | "Women" -> Some(item)
            | _ -> None)
    showStoreItems womenItems
btnWomenItems.Click.Add btnWomenItemsClick

let btnIncreasePricesBy10Percent = new Button(Location = new Point(330, 355), Width = 170, Text = "Increase Unit Prices by 10%")
departmentStore.Controls.Add btnIncreasePricesBy10Percent

let btnIncreasePricesBy10PercentClick _ =
    let sales = List.map (fun (item : SaleItem) -> new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize,
    								item.UnitPrice + (item.UnitPrice * 0.10), item.DaysInStore)
    		) salesItems
    showStoreItems sales
btnIncreasePricesBy10Percent.Click.Add btnIncreasePricesBy10PercentClick

let btnClose = new Button(Left = 780, Top = 355, Text = "Close")
let btnCloseClick e =
    departmentStore.Close()
btnClose.Click.Add btnCloseClick
departmentStore.Controls.Add btnClose

[<EntryPoint>]
let main argv =
    Application.Run departmentStore
    0

This would produce:

Mapping Some Elements

Mapping Some Elements

Partitioning a List

Partitioning a list consists of creating two lists derived from an existing one. To do this, you apply a condition to an existing list. The items that respond to the condition are put in a new list. The items that don't respond to the condition are put in another new list. To support this ôperation, the List module provides a function named partition. Its signature is:

List.partition : ('T -> bool) -> 'T list -> 'T list * 'T list

This function takes a function and a list as arguments. The function argument applies a condition to all members of the list. The function returns two lists. You can get the return value as two values or as a pair. Here is an example:

open System
open System.Windows.Forms

type OccupancyStatus =
| Other
| Available
| Occupied
| NeedsRepair

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

let apartments = [
    { UnitNumber = "101"; Bedrooms = 2; Bathrooms = 2.00; SecurityDeposit = 650; MonthlyRate = 1150; Status = OccupancyStatus.Available   }
    { UnitNumber = "102"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950; Status = OccupancyStatus.NeedsRepair }
    { UnitNumber = "103"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  925; Status = OccupancyStatus.Available   }
    { UnitNumber = "104"; Bedrooms = 3; Bathrooms = 2.00; SecurityDeposit = 850; MonthlyRate = 1350; Status = OccupancyStatus.Occupied    }
    { UnitNumber = "105"; Bedrooms = 2; Bathrooms = 1.00; SecurityDeposit = 550; MonthlyRate = 1150; Status = OccupancyStatus.Available   }
    { UnitNumber = "106"; Bedrooms = 3; Bathrooms = 1.50; SecurityDeposit = 950; MonthlyRate = 1425; Status = OccupancyStatus.NeedsRepair }
    { UnitNumber = "107"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950; Status = OccupancyStatus.Occupied    }
    { UnitNumber = "108"; Bedrooms = 2; Bathrooms = 1.00; SecurityDeposit = 550; MonthlyRate = 1150; Status = OccupancyStatus.Occupied    }
 ]

let (ready, not) = List.partition (fun (apart : Apartment) -> apart.Status = OccupancyStatus.Available) apartments

let apartmentsListing = new Form(Width = 355, Height = 310, Text = "Apartments Listing", MaximizeBox = false)

apartmentsListing.Controls.Add(new Label(Left = 12, Top = 12, Width = 200, Height = 13, Text = "Apartments - Ready to Rent"))

let lbxReadyApartments = new ListBox(Left = 12, Top = 32, Width = 320, Height = 98)

lbxReadyApartments.Items.Add("\t\t\tMonthly") |> ignore
lbxReadyApartments.Items.Add("Unit # Beds\tBaths\tRent\tDeposit\tStatus") |> ignore
lbxReadyApartments.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore
for apart in ready do
    lbxReadyApartments.Items.Add(sprintf "%s\t%i\t%.2f\t%d\t%d\t%A" apart.UnitNumber apart.Bedrooms apart.Bathrooms apart.MonthlyRate apart.SecurityDeposit apart.Status) |> ignore
lbxReadyApartments.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore

apartmentsListing.Controls.Add lbxReadyApartments

apartmentsListing.Controls.Add(new Label(Left = 12, Top = 135, Width = 200, Height = 13, Text = "Apartments - Not Ready for Rent"))

let lbxNotReadyApartments = new ListBox(Left = 12, Top = 152, Width = 320, Height = 130)

lbxNotReadyApartments.Items.Add("\t\t\tMonthly") |> ignore
lbxNotReadyApartments.Items.Add("Unit # Beds\tBaths\tRent\tDeposit\tStatus") |> ignore
lbxNotReadyApartments.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore
for apart in not do
    lbxNotReadyApartments.Items.Add(sprintf "%s\t%i\t%.2f\t%d\t%d\t%A" apart.UnitNumber apart.Bedrooms apart.Bathrooms apart.MonthlyRate apart.SecurityDeposit apart.Status) |> ignore
lbxNotReadyApartments.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore

apartmentsListing.Controls.Add lbxNotReadyApartments

do Application.Run apartmentsListing

This would produce:

Partitioning a List

Notice that the List.partition() function returns a tuple pair whose two elements are a list each. If one of both tuple members holds a list whose members can be segmented based on certain criterion, you can call the function on that member to get another tuple with sub-lists. Here is an example:

open System
open System.Windows.Forms

type OccupancyStatus =
| Other
| Available
| Occupied
| NeedsRepair

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

let apartments = [
    { UnitNumber = "101"; Bedrooms = 2; Bathrooms = 2.00; SecurityDeposit = 650; MonthlyRate = 1150; Status = OccupancyStatus.Available   }
    { UnitNumber = "102"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950; Status = OccupancyStatus.NeedsRepair }
    { UnitNumber = "103"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  925; Status = OccupancyStatus.Available   }
    { UnitNumber = "104"; Bedrooms = 3; Bathrooms = 2.00; SecurityDeposit = 850; MonthlyRate = 1350; Status = OccupancyStatus.Occupied    }
    { UnitNumber = "105"; Bedrooms = 2; Bathrooms = 1.00; SecurityDeposit = 550; MonthlyRate = 1150; Status = OccupancyStatus.Available   }
    { UnitNumber = "106"; Bedrooms = 3; Bathrooms = 1.50; SecurityDeposit = 950; MonthlyRate = 1425; Status = OccupancyStatus.NeedsRepair }
    { UnitNumber = "107"; Bedrooms = 1; Bathrooms = 1.00; SecurityDeposit = 500; MonthlyRate =  950; Status = OccupancyStatus.Occupied    }
    { UnitNumber = "108"; Bedrooms = 2; Bathrooms = 1.00; SecurityDeposit = 550; MonthlyRate = 1150; Status = OccupancyStatus.Occupied    }
 ]

//let (ready, not) = List.partition (fun (apart : Apartment) -> apart.Status = OccupancyStatus.Available) apartments
let (notAvailable, available) = List.partition (fun (apart : Apartment) -> apart.Status <> OccupancyStatus.Available) apartments
let (occupied, needRepair) = List.partition (fun (apart : Apartment) -> apart.Status = OccupancyStatus.Occupied) notAvailable

let apartmentsListing = new Form(Width = 355, Height = 395, Text = "Apartments Listing", MaximizeBox = false)

apartmentsListing.Controls.Add(new Label(Left = 12, Top = 12, Width = 200, Height = 13, Text = "Apartments - Available"))

let lbxReadyApartments = new ListBox(Left = 12, Top = 32, Width = 320, Height = 98)

lbxReadyApartments.Items.Add("\t\t\tMonthly") |> ignore
lbxReadyApartments.Items.Add("Unit # Beds\tBaths\tRent\tDeposit\tStatus") |> ignore
lbxReadyApartments.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore
for apart in available do
    lbxReadyApartments.Items.Add(sprintf "%s\t%i\t%.2f\t%d\t%d\t%A" apart.UnitNumber apart.Bedrooms apart.Bathrooms apart.MonthlyRate apart.SecurityDeposit apart.Status) |> ignore
lbxReadyApartments.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore

apartmentsListing.Controls.Add lbxReadyApartments

apartmentsListing.Controls.Add(new Label(Left = 12, Top = 135, Width = 200, Height = 13, Text = "Apartments - Occupied"))

let lbxNotReadyApartments = new ListBox(Left = 12, Top = 152, Width = 320, Height = 100)

lbxNotReadyApartments.Items.Add("\t\t\tMonthly") |> ignore
lbxNotReadyApartments.Items.Add("Unit # Beds\tBaths\tRent\tDeposit\tStatus") |> ignore
lbxNotReadyApartments.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore
for apart in occupied do
    lbxNotReadyApartments.Items.Add(sprintf "%s\t%i\t%.2f\t%d\t%d\t%A" apart.UnitNumber apart.Bedrooms apart.Bathrooms apart.MonthlyRate apart.SecurityDeposit apart.Status) |> ignore
lbxNotReadyApartments.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore

apartmentsListing.Controls.Add lbxNotReadyApartments

apartmentsListing.Controls.Add(new Label(Left = 12, Top = 255, Width = 200, Height = 13, Text = "Apartments - Need Repair"))

let lbxNeedRepair = new ListBox(Left = 12, Top = 272, Width = 320, Height = 90)

lbxNeedRepair.Items.Add("\t\t\tMonthly") |> ignore
lbxNeedRepair.Items.Add("Unit # Beds\tBaths\tRent\tDeposit\tStatus") |> ignore
lbxNeedRepair.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore
for apart in needRepair do
    lbxNeedRepair.Items.Add(sprintf "%s\t%i\t%.2f\t%d\t%d\t%A" apart.UnitNumber apart.Bedrooms apart.Bathrooms apart.MonthlyRate apart.SecurityDeposit apart.Status) |> ignore
lbxNeedRepair.Items.Add("----------------------------------------------------------------------------------------------------") |> ignore

apartmentsListing.Controls.Add lbxNeedRepair

do Application.Run apartmentsListing

This would produce:

Partitioning a List

   
   
 

Home Copyright © 2009-2015, FunctionX Home