Home

F# Classes: Mutually Recursive Types

   

Description

When it comes to classes and records, mutual recursion is a scenario where each type can refer to the other. The formula to implement it is:

type Type1
    class
    end
    
    and Type2
    class
    end
    
    and Type-n
    class
    end

The first type (and only the first) uses the type keyword. The keyword to connect the types is and. In the body of any type, you can refer to another type as if that other type exists already. Mutual recursion is used, and useful, with types that follow each other in the same document. The types don't have to have anything in common. They don't even have to share anything, and they don't have to communicate. Here is an example that uses classes:

type Contractor(code, name, sal, rate) =
    let cd = ref code
    let nm = ref name
    let sl = ref sal
    let rt = ref rate
    
    member this.ContractorCode with get() = !cd and set(value) = cd := value
    member this.FullName       with get() = !nm and set(value) = nm := value
    member this.BaseSalary     with get() = !sl and set(value) = sl := value
    member this.CommissionRate with get() = !rt and set(value) = rt := value

and Parallelogram(length, height) =
        let len  = ref length
        let hgt  = ref height
    
        member this.Length with get() = !len  and set(value) = len  := value
        member this.Height with get() = !hgt  and set(value) = hgt  := value
        member this.Area with get() = (fun a b -> !a * !b) len hgt

One of the reasons to apply mutual recursion to types is to exchange data among them. To do this, you can pass one type as parameter to the constructor of another recursion-involved class. Such a parameter is used in the body of the class the same way we have done when passing a class. Here is an example:

open System
open System.Windows.Forms

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

type Customer(acntNbr, name, speed : SpeedPackage) =
    let nbr = ref acntNbr
    let nm = ref name
    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 = "" with get, set
    member val City = "" with get, set
    member val County = "" with get, set
    member val State = "" with get, set
    member val ZIPCode = 10000 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.10
    member this.StateTax with get() =
                            match cust.Value.State with
                            | "DC" -> 0.35
                            | "MD" -> 0.26
                            | "VA" -> 0.12
                            | "WV" -> 0.28
                            | _ -> 0.20
    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.10
    member this.StateTax with get() =
                            match cust.Value.State with
                            | "DC" -> 0.85
                            | "MD" -> 0.60
                            | "VA" -> 0.44
                            | "WV" -> 0.68
                            | _ -> 0.50

let client940385294757, client472838257284, client295739475669 = new Customer("940-385294-757", "Brian Wiliey", Ultra), new Customer("472-838257-284", "Jeannette Meeder", Basic), new Customer("295-739475-669", "James Millers", Basic)

client940385294757.Address <- "8046 Stellar Rd"
client940385294757.City <- "Silver Spring"
client940385294757.County <- "Montgomery"
client940385294757.State <- "MD"
client940385294757.ZIPCode <- 20904
client940385294757.IncludesSportPackage <- true
client940385294757.IncludesDigitalService <- true

client472838257284.Address <- "12042 Rabbitt Rd"
client472838257284.City <- "Hyattsville"
client472838257284.County <- "Prince George's"
client472838257284.State <- "MD"
client472838257284.ZIPCode <- 20782
client472838257284.IncludesDigitalService <- false
client472838257284.IncludesDVRService <- true
client472838257284.IncludesSportPackage <- false
client472838257284.IncludesForeignPackage <- false
client472838257284.LeasingModem <- true
client472838257284.LeasingRemoteControl <- false

client295739475669.Address <- "622 Spurrier Ave."
client295739475669.City <- "Washington"
client295739475669.County <- "Washington"
client295739475669.State <- "DC"
client295739475669.ZIPCode <- 20008
client295739475669.IncludesForeignPackage <- true
client295739475669.IncludesDigitalService <- true
client295739475669.LeasingModem <- true
client295739475669.LeasingRemoteControl <- true

let mutable selectedClient = new Customer("", "", Basic)
let mutable cable = new CableBill(selectedClient)
let mutable web = new Internet(selectedClient)

let cableCompany = new Form()
cableCompany.Text   <- "Cable Company"
cableCompany.Width  <- 570
cableCompany.Height <- 570

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

cableCompany.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")
cableCompany.Controls.Add txtCableBasicService

cableCompany.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")
cableCompany.Controls.Add txtDigitalService

cableCompany.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")
cableCompany.Controls.Add txtDVRService

cableCompany.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")
cableCompany.Controls.Add txtForeignPackage

cableCompany.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")
cableCompany.Controls.Add txtCableCityTax

cableCompany.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")
cableCompany.Controls.Add txtModemFee

cableCompany.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")
cableCompany.Controls.Add txtRemoteControlFee

cableCompany.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")
cableCompany.Controls.Add txtCableCountyTax

cableCompany.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")
cableCompany.Controls.Add txtFCCFee

cableCompany.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")
cableCompany.Controls.Add txtRegulatoryFee

cableCompany.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")
cableCompany.Controls.Add txtCableStateTax

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

cableCompany.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")
cableCompany.Controls.Add txtInternetBasicService

cableCompany.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")
cableCompany.Controls.Add txtSpeedPackage

cableCompany.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")
cableCompany.Controls.Add txtInternetCityTax

cableCompany.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")
cableCompany.Controls.Add txtInternetCountyTax

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

cableCompany.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")
cableCompany.Controls.Add txtDiscountAmount

cableCompany.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")
cableCompany.Controls.Add txtInternetStateTax

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

cableCompany.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")
cableCompany.Controls.Add txtTotalCable

cableCompany.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")
cableCompany.Controls.Add txtFederalServiceFund

cableCompany.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")
cableCompany.Controls.Add txtTotalInternet

cableCompany.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")
cableCompany.Controls.Add txtRegulatoryRecoveryFee

cableCompany.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")
cableCompany.Controls.Add txtTotalAmountDue

let txtAccountNumberLostFocus e =
    match txtAccountNumber.Text with
    | "940-385294-757" -> selectedClient <- client940385294757
    | "472-838257-284" -> selectedClient <- client472838257284
    | "295-739475-669" -> selectedClient <- client295739475669
    | _ ->
        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

    txtCustomerName.Text <- !selectedClient.CustomerName
    txtAddress.Text <- selectedClient.Address
    txtCity.Text <- selectedClient.City
    txtCounty.Text <- selectedClient.County 
    txtState.Text <- selectedClient.State 
    txtZIPCode.Text <- sprintf "%i" selectedClient.ZIPCode 
    chkIncludesDigitalService.Checked <- selectedClient.IncludesDigitalService 
    chkIncludesDVRService.Checked <- selectedClient.IncludesDVRService 
    chkIncludesSportPackage.Checked <- selectedClient.IncludesSportPackage 
    chkIncludesForeignPackage.Checked <- selectedClient.IncludesForeignPackage 
    chkLeasingModem.Checked <- selectedClient.LeasingModem 
    chkLeasingRemoteControl.Checked <- selectedClient.LeasingRemoteControl

    cable <- new CableBill(selectedClient)
    web <- new Internet(selectedClient)

    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

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
cableCompany.Controls.Add btnCalculate

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

do Application.Run cableCompany

Here an example of testing the application:

Mutually Recursive Classes

Mutually Recursive Classes

Mutually Recursive Classes

Remember that one or any of the types can be a record. Here is an example:

type Customer = {
    AccountNumber : string
    FirstName     : string
    LastName      : string }

and GasBill(billNbr, client : Customer, meterReading) =
    let bNbr = ref billNbr
    let cust = ref client
    . . .

Other than that, you can follow the rules of each when using it in your program.

   
     
 

Home Copyright © 2015 FunctionX Home