2015-12-24 11 views
5

Tra molti altri tipi di Stato legati, ho i seguenti tipi di record nel mio codice:Copia proprietà tra i record

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 
} 

Questi poi ad alimentare la seguente:

type Suggestion = 
    | SubmittedSuggestion of SubmittedSuggestionData 
    | ApprovedSuggestion of ApprovedSuggestionData 

Questo mi la dà capacità di lavorare con un modello di stile macchina dello stato per eseguire specifiche logiche di business dipendenti dallo stato. (Questo approccio è stato preso da: http://fsharpforfunandprofit.com/posts/designing-with-types-representing-states/)

Ho una funzione che nella sua forma più semplice, cambia un SubmittedSuggestion a un ApprovedSuggestion:

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

Questa funzione è incompleta al momento come quello che sto lottando comprendere quando un suggerimento cambia da Inviato a Approvato, come si copiano le proprietà dal passato in suggestion nel nuovo ApprovedSuggestion appena inserito anche nella nuova proprietà di ApprovalDate?

immagino che avrebbe funzionato se ho fatto qualcosa di simile:

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} 

ma che sembra piuttosto orribile per me.

Esiste un modo più pulito e più sintetico per ottenere lo stesso risultato? Ho provato a utilizzare la parola chiave with ma non è stata compilata.

Grazie

risposta

6

Se c'è una grande sovrapposizione tra i tipi, è spesso una buona idea pensare sulla decomposizione. Ad esempio, i tipi potrebbe essere la seguente:

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

type ApprovedSuggestionData = { 
    Suggestion : SuggestionData 
    ApprovalDate : DateTime 
} 

A seconda dell'uso e le differenze tra i tipi, si potrebbe anche considerare di avere il tipo omologato solo all'interno dell'Unione discriminati, saltando il secondo tipo del tutto:

Un argomento comune contro tale decomposizione è che l'accesso ai membri di tipi più in basso nella gerarchia diventa più dettagliato, ad esempio approvedSuggestionData.Suggestion.Originator. Anche se questo è vero, le proprietà possono essere utilizzate per inoltrare membri di componenti di uso comune se la verbosità diventa fastidiosa e qualsiasi svantaggio dovrebbe essere valutato rispetto ai vantaggi: c'è meno duplicazione di codice nei tipi e qualsiasi operazione che i tipi più granulari offrono essere reso disponibile dal tipo composto.

La possibilità di costruire facilmente un suggerimento approvato da un suggerimento non approvato e la data di approvazione è un caso in cui ciò è utile. Ma potrebbe esserci di più: ad esempio, c'è un'operazione che convalida gli utenti e le categorie di tutti i suggerimenti, approvati o meno. Se i tipi che contengono i membri Originator e ParentCategory per i suggerimenti approvati e non approvati non sono correlati, il codice che ottiene questi deve essere duplicato.(O un'interfaccia comune dovrebbe essere creata.)

+0

Hmmm, mi piace l'aspetto di questo @Vandroiy. I miei stati non cambieranno drasticamente, quindi questa potrebbe essere una soluzione valida per me. Darò un colpo e ti faccio sapere :) – Stu1986C

4

vorrei cambiare il tuo suggerimento per

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

e quindi l'approvazione diventa

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

Grazie a @John Palmer. Mi è venuto in mente di farlo, ma sono leggermente odiato dal fatto che ho usato più volte uno schema di stato in C# e la cosa che mi ha fatto rabbrividire alle mie precedenti implementazioni è la presenza di proprietà non necessarie all'interno di un certo stato. Idealmente mi piacerebbe un modello di dominio pulito in cui solo le proprietà rilevanti sono state trovate nei rispettivi tipi di record. :) – Stu1986C

+0

Quella proprietà non è realmente "inutilizzata", indica se il suggerimento è approvato o meno. –

4

Entrambi i suggerimenti offerti da @Vandroiy e @JohnPalmer sono buoni, ma per completezza, vorrei offrire anche una terza prospettiva.

Per quanto ne so, non esiste un costrutto linguistico che faccia succedere la copia di valori tra tipi "simili". Il motivo è che questi tipi sono diversi. La loro somiglianza è incidentale, ma vista dal sistema tipo, sono tipi completamente diversi. Se li guardi "matematicamente", sono semplicemente i tipi A e B.

In questi casi, mi piacerebbe spesso semplicemente ingoiare il rospo e aggiungere una funzione di traduzione che traduce i valori di un tipo a valori di un altro tipo:

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

Mentre sembra un po 'prolisso, lo stai ancora mantenendo ASCIUTTO perché tale copia di valori è vincolata a quella singola funzione.

Inoltre, a tale funzione può essere spesso assegnato un buon nome, il che significa che la funzione diventa parte del modello di dominio.

Problemi correlati