在其他几个与State相关的类型中,我的代码中有以下记录类型:
type SubmittedSuggestionData = {
SuggestionId : Guid
SuggestionText : string
Originator : User
ParentCategory : Category
SubmissionDate : DateTime
}
type ApprovedSuggestionData = {
SuggestionId : Guid
SuggestionText : string
Originator : User
ParentCategory : Category
SubmissionDate : DateTime
ApprovalDate : DateTime
}
然后将这些内容输入以下内容:
type Suggestion =
| SubmittedSuggestion of SubmittedSuggestionData
| ApprovedSuggestion of ApprovedSuggestionData
这使我能够使用状态机样式模式来执行依赖于State的特定业务逻辑。 (这种方法取自:http://fsharpforfunandprofit.com/posts/designing-with-types-representing-states/)
我有一个函数,以最简单的形式,将SubmittedSuggestion
更改为ApprovedSuggestion
:
let ApproveSuggestion suggestion =
match suggestion with
| SubmittedSuggestion suggestion -> ApprovedSuggestion {}
此功能暂时不完整,因为我正在努力理解的是当Suggestion从Subved变为Approved时,如何将传递的suggestion
中的属性复制到新创建的{{1同时还填充ApprovedSuggestion
的新属性?
我想如果我做了类似的话会有用:
ApprovalDate
但这对我来说非常可怕。
是否有更清洁,更简洁的方法来获得相同的结果?我尝试使用let ApproveSuggestion suggestion =
match suggestion with
| SubmittedSuggestion {SuggestionId = suggestionId; SuggestionText = suggestionText; Originator = originator; ParentCategory = category; SubmissionDate = submissionDate} ->
ApprovedSuggestion {SuggestionId = suggestionId; SuggestionText = suggestionText; Originator = originator; ParentCategory = category; SubmissionDate = submissionDate; ApprovalDate = DateTime.UtcNow}
关键字,但没有编译。
由于
答案 0 :(得分:6)
如果类型之间存在大量重叠,那么考虑分解通常是个好主意。例如,类型可能如下所示:
type SuggestionData = {
SuggestionId : Guid
SuggestionText : string
Originator : User
ParentCategory : Category
SubmissionDate : DateTime
}
type ApprovedSuggestionData = {
Suggestion : SuggestionData
ApprovalDate : DateTime
}
根据类型之间的用法和差异,人们还可以考虑仅在受歧视的联合中使用已批准的类型,完全跳过第二种类型:
type Suggestion =
| SubmittedSuggestion of SuggestionData
| ApprovedSuggestion of SuggestionData * approvalDate : DateTime
反对这种分解的一个常见论点是,对层次结构深层次类型成员的访问变得更加冗长,例如: approvedSuggestionData.Suggestion.Originator
。虽然这是真的,但如果冗长变得令人讨厌,则可以使用属性来转发常用的成员成员,并且应该权衡任何缺点:类型中的代码重复较少,以及更细粒度类型提供的任何操作都可以可以从组合类型中获得。
从未经批准的建议和批准日期轻松构建批准建议的能力是一个有用的案例。但可能会有更多:例如,有一项操作可以验证所有建议的用户和类别,无论是否批准。如果持有Originator
和ParentCategory
成员的类型与已批准和未批准的建议无关,则需要复制获取这些建议的代码。 (或者需要创建一个通用接口。)
答案 1 :(得分:4)
我会将你的建议改为
type SubmittedSuggestionData = {
SuggestionId : Guid
SuggestionText : string
Originator : User
ParentCategory : Category
SubmissionDate : DateTime
ApprovalDate : DateTime option
}
然后批准成为
let approve t = {t with AprovalDate =Some(System.DateTime.Now)}
答案 2 :(得分:4)
@Vandroiy和@JohnPalmer提供的建议都很好,但为了完整起见,我还想提供第三个观点。
据我所知,没有任何语言结构可以使'相似'类型之间的值复制简洁。原因是这些类型不同。它们的相似性是偶然的,但从类型系统看,它们是完全不同的类型。如果你“以数学方式”查看它们,它们只是类型A
和B
。
在这种情况下,我经常只是咬紧牙关并添加翻译功能,将一种类型的值转换为另一种类型的值:
let approve (suggestion : SubmittedSuggestionData) = {
SuggestionId = suggestion.SuggestionId
SuggestionText = suggestion.SuggestionText
Originator = suggestion.Originator
ParentCategory = suggestion.ParentCategory
SubmissionDate = suggestion.SubmissionDate
ApprovalDate = DateTime.UtcNow }
虽然它看起来有点冗长,但你仍然保持干燥,因为这种值的复制仅限于那个单一的功能。
此外,这样的功能通常可以给出一个好名字,这意味着该功能将成为您的域模型的一部分。