在F#联合类型列表上运行

时间:2012-12-10 15:57:10

标签: list f# discriminated-union

这是F# List of Union Types的问题的延续。感谢有用的反馈,我能够创建Report的列表,其中ReportDetailSummary。以下是数据定义:

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

现在我已经在名为Report的变量中获得了reports的列表,我想迭代这些Report个对象并根据每个对象执行操作。除了处理Detail.DivisionsSummary.Office的情况外,操作是相同的。显然,我必须以不同的方式处理这些问题。但我不想复制所有代码来处理每个代码StateSections

我的第一个(工作)想法如下:

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

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

    ...

这会让我知道何时处理Detail.Divisions而不是Summary.Office。但它并没有给我一个可以使用的对象。我仍然坚持report,不知道它是Detail还是Summary,也无法访问这些属性。我想将report转换为相应的DetailSummary,然后使用相同的代码处理这两种情况,但Detail.Divisions和{{除外1}}。有没有办法做到这一点?

感谢。

5 个答案:

答案 0 :(得分:6)

你可以这样做:

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

答案 1 :(得分:4)

@kvb的答案可能是我使用的方法,如果我有你描述的数据结构。但是,我认为考虑您拥有的数据类型是否是最佳表示是有意义的。

DetailSummary共享两个属性(StateSections)的事实可能意味着Report有一些共同的部分无论报告的类型如何,都可以共享(如果报告详细,报告可以添加Divisions,如果是摘要,报告可以Office添加。)

使用以下内容可以更好地表达(Section保持不变,因此我没有将其包含在代码段中):

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

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

如果您使用此样式,则只需访问report.Statereport.Sections(执行处理的常见部分),然后您就可以在report.Information上进行匹配以执行不同的部分处理。

编辑 - 回答Jeff的评论 - 如果数据结构已经修复,但视图已更改,则可以使用F#活动模式来编写“适配器”使用我上面描述的视图提供对旧数据结构的访问:

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

第一个活动模式总是成功并提取公共部分。第二个允许您区分这两种情况。然后你可以写:

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

这实际上是F#活动模式如何提供允许隐藏实现细节的抽象的一个很好的例子。

答案 2 :(得分:3)

kvb的答案很好,可能就是我会用的。但是你表达问题的方式听起来像你想要经典继承。

type ReportPart(state, sections) =
  member val State = state
  member val Sections = sections

type Detail(state, sections, divisions) =
  inherit ReportPart(state, sections) 
  member val Divisions = divisions

type Summary(state, sections, office) =
  inherit ReportPart(state, sections) 
  member val Office = office

然后你可以做到你所期望的:

for report in reports do
  match report with
  | :? Detail as detail ->   //use detail.Divisions
  | :? Summary as summary -> //use summary.Office
  //use common properties

答案 3 :(得分:2)

当您匹配并使用单独的函数处理DetailSummary值时,您可以在每个联合案例中的DivisionsOffice记录上进行模式匹配,例如

let blah =
    for report in reports do
        let out = match report with
        | Detail({ State = state; Divisions = divisions; Sections = sections } as d) -> 
            Detail({ d with Divisions = (handleDivisions divisions) })
        | Summary({ State = state; Office = office; Sections = sections } as s) -> 
            Summary( { s with Office = handleOffice office })

    //process out

答案 4 :(得分:2)

您可以重构代码以使每个公共字段具有实用程序功能并使用嵌套模式匹配:

let handleReports reports =
   reports |> List.iter (function 
                  | Detail {State = s; Sections = ss; Divisions = ds} ->
                     handleState s
                     handleSections ss
                     handleDivisions ds
                  | Summary {State = s; Sections = ss; Office = o} ->
                     handleState s
                     handleSections ss
                     handleOffice o)

您还可以过滤DetailSummary,以便在不同的功能中单独处理它们:

let getDetails reports =
    List.choose (function Detail d -> Some d | _ -> None) reports 

let getSummaries reports =
    List.choose (function Summary s -> Some s | _ -> None) reports