简体   繁体   English

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

[英]Operating on an F# List of Union Types

This is a continuation of my question at F# List of Union Types . 这是我在F#联盟类型列表中的问题的延续。 Thanks to the helpful feedback, I was able to create a list of Report s, with Report being either Detail or Summary . 感谢有用的反馈,我能够创建一个Report列表, Report可以是DetailSummary 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 Report s in a variable called reports , I want to iterate over those Report objects and perform operations based on each one. 既然我已经在一个名为reports的变量中获得了Report的列表,我想迭代这些Report对象并根据每个对象执行操作。 The operations are the same except for the cases of dealing with either Detail.Divisions or Summary.Office . 该操作是,除了应对两种情况中的相同Detail.DivisionsSummary.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. 但是我不想复制处理每个类似StateSections的所有代码。

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 . 这会给我一个方法来知道什么时候处理Detail.Divisions而非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. 我仍然坚持使用report ,不知道它是什么, DetailSummary ,也无法访问属性。 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 . 我想转换report ,以适当的DetailSummary ,然后使用相同的代码来处理这两种情况下,与外Detail.DivisionsSummary.Office Is there a way to do this? 有没有办法做到这一点?

Thanks. 谢谢。

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

The answer by @kvb is probably the approach I would use if I had the data structure you described. @kvb的答案可能是我使用的方法,如果我有你描述的数据结构。 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). 事实上, DetailSummary共享两个属性( StateSections )可能意味着Report某些共同部分是共享的,无论报告的类型如何(如果报告详细,报告可以添加Divisions或只是Office ,如果是摘要)。

Something like that would be better expressed using the following ( Section stays the same, so I did not include it in the snippet): 使用以下内容可以更好地表达类似的内容( Section保持不变,因此我没有将其包含在代码段中):

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. 如果您使用此样式,则只需访问report.Statereport.Sections (执行处理的常见部分),然后您可以匹配report.Information以执行处理的不同部分。

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: 编辑 -回答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

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. 这实际上是F#活动模式如何提供允许您隐藏实现细节的抽象的一个很好的示例。

kvb's answer is good, and probably what I would use. kvb的答案很好,可能就是我会用的。 But the way you've expressed your problem sounds like you want classic inheritance. 但是你表达问题的方式听起来像你想要经典继承。

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

Then you can do precisely what you expect: 然后你可以完全按照你的期望做到:

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

You can pattern match on the Detail or Summary record in each of the union cases when you match and handle the Divisions or Office value with a separate function eg 当您匹配并使用单独的函数处理DivisionsOffice值时,您可以在每个联合案例中的DetailSummary记录上进行模式匹配,例如

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

You can refactor the code to have a utility function for each common field and use nested pattern matching: 您可以重构代码以使每个公共字段具有实用程序功能并使用嵌套模式匹配:

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)

You can also filter Detail and Summary to process them separately in different functions: 您还可以筛选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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM