Copy properties between records

Among several other state-related types, I have the following types of records in my code:

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 } 

Then they are passed to the following:

 type Suggestion = | SubmittedSuggestion of SubmittedSuggestionData | ApprovedSuggestion of ApprovedSuggestionData 

This gives me the opportunity to work with the State machine style template to execute specific state-specific business logic. (This approach was taken from: http://fsharpforfunandprofit.com/posts/designing-with-types-representing-states/ )

I have a function that in its simplest form changes a SubmittedSuggestion to ApprovedSuggestion :

 let ApproveSuggestion suggestion = match suggestion with | SubmittedSuggestion suggestion -> ApprovedSuggestion {} 

This function is not complete at the moment, because what I'm trying to understand is when the sentence changes from "Presented in Approved", how do you copy the properties from the passed in suggestion to the newly created ApprovedSuggestion , as well as filling out the new ApprovalDate property?

I think this will work if I do something like:

 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} 

but it looks pretty terrible to me.

Is there a cleaner, more concise way to get the same result? I tried using the with keyword, but it did not compile.

thanks

+5
source share
3 answers

If there is a big match between the types, it is often nice to think about decomposition. For example, types may look like this:

 type SuggestionData = { SuggestionId : Guid SuggestionText : string Originator : User ParentCategory : Category SubmissionDate : DateTime } type ApprovedSuggestionData = { Suggestion : SuggestionData ApprovalDate : DateTime } 

Depending on the use and differences between the types, it can also be considered that the approved type is only within the recognized union, generally skipping the second type:

 type Suggestion = | SubmittedSuggestion of SuggestionData | ApprovedSuggestion of SuggestionData * approvalDate : DateTime 

A common argument against this decomposition is that access to type members more deeply down the hierarchy becomes more detailed, for example. approvedSuggestionData.Suggestion.Originator . Although this is true, properties can be used to forward commonly used members of constituents if verbosity becomes annoying and any disadvantage should be weighed against the advantages: there is less duplication of code in types, and any operation that more granular types offer can be accessed from compiled type.

The ability to easily draw up an approved proposal from an unauthorized proposal and the date of approval is one time this is useful. But there can be more: let's say, for example, there is an operation that checks users and categories of all proposals, approved or not. If the types containing the Originator and ParentCategory for the approved and unapproved proposals are not related to each other, the code that should duplicate them. (Or you need to create a common interface.)

+6
source

I would change your offer to

 type SubmittedSuggestionData = { SuggestionId : Guid SuggestionText : string Originator : User ParentCategory : Category SubmissionDate : DateTime ApprovalDate : DateTime option } 

and then the statement becomes

 let approve t = {t with AprovalDate =Some(System.DateTime.Now)} 
+4
source

Both suggestions offered by @Vandroiy and @JohnPalmer are good, but for completeness, I would also like to offer a third perspective.

As far as I know, there is no language construct that makes copying values ​​between "similar" types concise. The reason for this is because these types are different. Their similarities are random, but can be seen from the type system, they are completely different types. If you look at them “mathematically,” they are just of type A and B

In such cases, I often just bit the bullet and added a translation function that translates the values ​​of one type to the values ​​of another type:

 let approve (suggestion : SubmittedSuggestionData) = { SuggestionId = suggestion.SuggestionId SuggestionText = suggestion.SuggestionText Originator = suggestion.Originator ParentCategory = suggestion.ParentCategory SubmissionDate = suggestion.SubmissionDate ApprovalDate = DateTime.UtcNow } 

Although this seems a little verbose, you still keep it DRY, because such copying of values ​​is limited to this single function.

In addition, such a function can often be given a good name, which means that this function becomes part of your domain model.

+4
source

Source: https://habr.com/ru/post/1239128/


All Articles