Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Operating on an F# List of Union Types

This is a continuation of my question at F# List of Union Types. Thanks to the helpful feedback, I was able to create a list of Reports, with Report being either Detail or Summary. Here's the data definition once more:

module Data

type Section = { Header: string;
                 Lines:  string list;
                 Total:  string }

type Detail = { State:     string;
                Divisions: string list;
                Sections:  Section list }

type Summary = { State:    string;
                 Office:   string;
                 Sections: Section list }

type Report = Detail of Detail | Summary of Summary

Now that I've got the list of Reports in a variable called reports, I want to iterate over those Report objects and perform operations based on each one. The operations are the same except for the cases of dealing with either Detail.Divisions or Summary.Office. Obviously, I have to handle those differently. But I don't want to duplicate all the code for handling the similar State and Sections of each.

My first (working) idea is something like the following:

for report in reports do
    let mutable isDetail  = false
    let mutable isSummary = false

    match report with
    | Detail  _ -> isDetail  <- true
    | Summary _ -> isSummary <- true

    ...

This will give me a way to know when to handle Detail.Divisions rather than Summary.Office. But it doesn't give me an object to work with. I'm still stuck with report, not knowing which it is, Detail or Summary, and also unable to access the attributes. I'd like to convert report to the appropriate Detail or Summary and then use the same code to process either case, with the exception of Detail.Divisions and Summary.Office. Is there a way to do this?

Thanks.

like image 250
Jeff Maner Avatar asked Dec 03 '22 01:12

Jeff Maner


2 Answers

You could do something like this:

for report in reports do
    match report with
    | Detail { State = s; Sections = l }
    | Summary { State = s; Sections = l } ->
        // common processing for state and sections (using bound identifiers s and l)

    match report with
    | Detail { Divisions = l } ->
        // unique processing for divisions
    | Summary { Office = o } ->
        // unique processing for office
like image 67
kvb Avatar answered Jan 05 '23 18:01

kvb


The answer by @kvb is probably the approach I would use if I had the data structure you described. However, I think it would make sense to think whether the data types you have are the best possible representation.

The fact that both Detail and Summary share two of the properties (State and Sections) perhaps implies that there is some common part of a Report that is shared regardless of the kind of report (and the report can either add Divisions if it is detailed or just Office if if is summary).

Something like that would be better expressed using the following (Section stays the same, so I did not include it in the snippet):

type ReportInformation = 
  | Divisions of string list
  | Office of string

type Report = 
  { State       : string;
    Sections    : Section list 
    Information : ReportInformation }

If you use this style, you can just access report.State and report.Sections (to do the common part of the processing) and then you can match on report.Information to do the varying part of the processing.

EDIT - In answer to Jeff's comment - if the data structure is already fixed, but the view has changed, you can use F# active patterns to write "adaptor" that provides access to the old data structure using the view that I described above:

let (|Report|) = function
  | Detail dt -> dt.State, dt.Sections
  | Summary st -> st.State, st.Sections

let (|Divisions|Office|) = function
  | Detail dt -> Divisions dt.Divisions
  | Summary st -> Office st.Office

The first active pattern always succeeds and extracts the common part. The second allows you to distinguish between the two cases. Then you can write:

let processReport report =
  let (Report(state, sections)) = report
  // Common processing
  match report wiht
  | Divisions divs -> // Divisions-specific code
  | Office ofc -> // Offices-specific code

This is actually an excellent example of how F# active patterns provide an abstraction that allows you to hide implementation details.

like image 30
Tomas Petricek Avatar answered Jan 05 '23 20:01

Tomas Petricek