2016-01-18 16 views
6

Sto cercando di praticare Domain Driven Design in F # e ci siamo imbattuti alla seguente domanda:Quando devo usare un record su una tupla?

Perché dovrei utilizzare un record quando si utilizza una tupla sembra richiedere meno sintassi e sembra essere più potente per quanto riguarda il pattern matching e solo scenari di utilizzo generali?

Ad esempio, mi sento come se non dovessi più creare un tipo di record solo per implementare un'unione discriminata se dovessi utilizzare una tupla.

type Name = { First:string 
       Middle:string option 
       Last:string } 

type Duration = { Hours:int 
        Minutes:int 
        Seconds:int } 

type Module = 
    | Author of Name 
    | Title of string 
    | Duration of Duration 

let tryCreateName (first, middle, last) = 
    { First=first; Middle=Some middle; Last=last } 

let tryCreateDuration (hours, minutes, seconds) = 
    { Hours=hours; Minutes=minutes;Seconds=seconds } 

let name = tryCreateName ("Scott", "K", "Nimrod") 

let hours = 1 
let minutes = 30 
let seconds = 15 

let duration = tryCreateDuration (hours, minutes, seconds) 

I miei pensieri sono accurati?

Le tuple sono desiderate su record nella maggior parte degli scenari?

+1

Vorrei rispondere ma so che Tomas farà di meglio. IIRC ha più a che fare con il lavoro con C# e .NET. Se sei puramente in F #, vai con la tupla. Se stai lavorando con un altro codice .NET, vai con il record. Ci sono altri fattori, ad esempio se è grande andare con un record, se non è pubblico andare con una tupla, ecc. Inoltre prima di chiedere anche di vedere [Quando usare classi, unioni, record e strutture] (https: // msdn.microsoft.com/en-us/library/dd233205.aspx) alla fine del collegamento. –

+1

Hai visto tutte le informazioni nel [tag F #] (http://stackoverflow.com/tags/f%23/info)? –

+0

[Le linee guida per la progettazione dei componenti F #] (http://fsharp.org/specs/component-design-guidelines/) –

risposta

12

Per la modellazione del dominio, si consiglia di utilizzare i tipi con elementi denominati ; cioè, record, unione discriminata e forse la classe o l'interfaccia occasionale.

Strutturalmente, record e tuple sono simili; in termini di tipo di dati algebrici, sono entrambi i tipi di prodotto .

La differenza è che con le tuple, l'ordine dei valori è importante e il ruolo di ogni elemento è implicito.

> (2016, 1, 2) = (2016, 1, 2);; 
val it : bool = true 
> (2016, 1, 2) = (2016, 2, 1);; 
val it : bool = false 

Nell'esempio precedente, è possibile indovinare che queste date di modello di tuple, ma quali esattamente? È il secondo di gennaio 2016? O è il primo di febbraio 2016?

con i record, d'altra parte, l'ordine degli elementi non importa, perché li si associa, e accedervi, per nome:

> type Date = { Year : int; Month : int; Day : int };; 

type Date = 
    {Year: int; 
    Month: int; 
    Day: int;} 

> { Year = 2016; Month = 1; Day = 2 } = { Year = 2016; Day = 2; Month = 1 };; 
val it : bool = true 

E 'anche più chiaro quando si vuole tirare fuori il valori costitutivi. Si può facilmente ottenere l'anno da un valore record di:

> let d = { Year = 2016; Month = 1; Day = 2 };; 

val d : Date = {Year = 2016; 
       Month = 1; 
       Day = 2;} 

> d.Year;; 
val it : int = 2016 

E 'molto più difficile da tirare i valori di una tupla:

> let d = (2016, 1, 2);; 

val d : int * int * int = (2016, 1, 2) 

> let (y, _, _) = d;; 

val y : int = 2016 

scontato, per le coppie, è possibile utilizzare le funzioni incorporate fst e snd per accedere agli elementi, ma per le tuple con tre o più elementi, non è possibile ottenere facilmente i valori a meno che il modello non corrisponda.

Anche se si definiscono funzioni personalizzate, il ruolo di ogni elemento è ancora implicitamente definito dal suo ordinale. È facile ottenere l'ordine dei valori errati se sono dello stesso tipo.

Quindi, per la modellazione del dominio, preferisco sempre i tipi espliciti, in modo che sia chiaro cosa sta succedendo.

Le tuple non sono mai appropriate, quindi?

Le tuple sono utili in altri contesti. Quando è necessario utilizzare un tipo ad hoc per comporre funzioni, sono più appropriate dei record.

Si consideri, ad esempio, Seq.zip, che consente di combinare due sequenze:

let alphalues = Seq.zip ['A'..'Z'] (Seq.initInfinite ((+) 1)) |> Map.ofSeq;; 

val alphalues : Map<char,int> = 
    map 
    [('A', 1); ('B', 2); ('C', 3); ('D', 4); ('E', 5); ('F', 6); ('G', 7); 
    ('H', 8); ('I', 9); ...] 

> alphalues |> Map.find 'B';; 
val it : int = 2 

Come si può vedere in questo esempio, tuple sono solo un passo verso il vero obiettivo, che è una mappa di valori alfabetici. Sarebbe imbarazzante se dovessimo definire un tipo di record ogni volta che volessimo comporre valori all'interno di un'espressione. Le tuple sono adatte per questo compito.

+0

"Con i record, d'altro canto, l'ordine degli elementi non ha importanza" ... Talvolta, ciò che conta è, ad esempio, quando le istanze del record vengono confrontate o ordinate, in quanto i record forniscono il confronto strutturale per impostazione predefinita. –

+1

@MarcSigrist Puoi approfondire questo? Come ho mostrato sopra, '{Anno = 2016; Mese = 1; Day = 2} 'è uguale a' {Year = 2016; Giorno = 2; Month = 1} ', anche se l'ordine in cui gli elementi sono elencati non è lo stesso. –

+1

L'ordine degli elementi è importante nel tipo di record declaration_, poiché l'implementazione generata da compilatore di IComparable dipende da esso. –

1

Potrebbero essere interessati a optional parameters, che sono consentiti solo sui tipi. Per evitare di cambiare l'ordine dei tuoi argomenti, ho reso opzionale anche il cognome (per esempio anche modellando meglio il dominio del tuo problema, ad esempio quando incontri Mononyms).

Per rappresentare una differenza tra due punti nel tempo è già presente una funzione di libreria nel framework, System.TimeSpan.

vostro esempio potrebbe quindi essere scritto come

type Name = { 
    FirstName : string 
    MiddleName : string option 
    LastName : string option } with 
    static member Create(firstName, ?middleName, ?lastName) = 
     { FirstName = firstName 
      MiddleName = middleName 
      LastName = lastName } 

type System.TimeSpan with 
    static member CreateDuration(hours, ?minutes, ?seconds) = 
     System.TimeSpan(
      hours, 
      defaultArg minutes 0, 
      defaultArg seconds 0) 

let name = Name.Create("Scott", "K", "Nimrod") 
let duration = System.TimeSpan.CreateDuration(1, 30, 15) 

L'idea è di fornire extension methods che accettano argomenti tupled di varia lunghezza, e restituisce una struttura dati complessa, ad esempio un record o una struttura.