这是F# List of Union Types的问题的延续。感谢有用的反馈,我能够创建Report
的列表,其中Report
为Detail
或Summary
。以下是数据定义:
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.Divisions
或Summary.Office
的情况外,操作是相同的。显然,我必须以不同的方式处理这些问题。但我不想复制所有代码来处理每个代码State
和Sections
。
我的第一个(工作)想法如下:
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
转换为相应的Detail
或Summary
,然后使用相同的代码处理这两种情况,但Detail.Divisions
和{{除外1}}。有没有办法做到这一点?
感谢。
答案 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的答案可能是我使用的方法,如果我有你描述的数据结构。但是,我认为考虑您拥有的数据类型是否是最佳表示是有意义的。
Detail
和Summary
共享两个属性(State
和Sections
)的事实可能意味着Report
有一些共同的部分无论报告的类型如何,都可以共享(如果报告详细,报告可以添加Divisions
,如果是摘要,报告可以Office
添加。)
使用以下内容可以更好地表达(Section
保持不变,因此我没有将其包含在代码段中):
type ReportInformation =
| Divisions of string list
| Office of string
type Report =
{ State : string;
Sections : Section list
Information : ReportInformation }
如果您使用此样式,则只需访问report.State
和report.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)
当您匹配并使用单独的函数处理Detail
或Summary
值时,您可以在每个联合案例中的Divisions
或Office
记录上进行模式匹配,例如
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)
您还可以过滤Detail
和Summary
,以便在不同的功能中单独处理它们:
let getDetails reports =
List.choose (function Detail d -> Some d | _ -> None) reports
let getSummaries reports =
List.choose (function Summary s -> Some s | _ -> None) reports