2016-01-18 14 views
6

Próbuję ćwiczyć Domain Driven Design w F # i natknął się na następujące pytanie:Kiedy należy używać rekordu na krotce?

Dlaczego chciałbym używać rekord podczas korzystania krotki wydaje się wymagać mniej składnie i wydaje się być mocniejszy w odniesieniu do dopasowywania wzorców i tylko ogólne scenariusze użycia?

Na przykład czuję się tak, jakbym nie musiał już tworzyć typu rekordu tylko po to, aby wprowadzić dyskryminowaną unię, gdybym zamiast tego używał krotki.

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) 

Czy moje myśli są dokładne?

Czy krotki są pożądane w stosunku do rekordów w większości scenariuszy?

+1

Chciałbym odpowiedzieć, ale wiem, że Tomas będzie lepiej. IIRC ma więcej wspólnego z pracą z C# i .NET. Jeśli jesteś wyłącznie w F #, idź z krotką. Jeśli pracujesz z innym kodem .NET, przejdź do rekordu. Są też inne czynniki, takie jak duże rozmiary z rekordem, jeśli nie są publiczne z krotką, itd. Przed pytaniem zobacz także [Kiedy używać klas, związków, rekordów i struktur] (https: // msdn.microsoft.com/en-us/library/dd233205.aspx) na końcu łącza. –

+1

Czy widziałeś wszystkie informacje w tagu [F #] (http://stackoverflow.com/tags/f%23/info)? –

+0

[Wytyczne dotyczące projektowania komponentów F #] (http://fsharp.org/specs/component-design-guidelines/) –

Odpowiedz

12

Do modelowania w domenie polecam używanie typów o nazwach o nazwie elementy:; Oznacza to, że rekordy, dyskryminowany związek i być może sporadyczna klasa lub interfejs.

Strukturalnie rekordy i krotki są podobne; w algebraicznym typie danych, oba typy to typy produktów.

Różnica polega na tym, że w przypadku krotek liczy się kolejność wartości, a rola każdego elementu jest niejawna.

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

W powyższym przykładzie możesz zgadnąć, że te krotki są datami modelu, ale które dokładnie? Czy to drugi stycznia 2016? Czy jest to pierwszy lutego 2016?

Z zapisów, z drugiej strony, kolejność elementów nie ma znaczenia, bo je związać, a dostęp do nich, o nazwie:

> 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 

To także wyraźniejsze, gdy chcemy wysunąć wartości składowe. Można łatwo uzyskać lat od wartości rekordu:

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

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

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

To znacznie trudniej wyciągnąć wartości z krotka:

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

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

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

val y : int = 2016 

pewnik, dla par, można użyć wbudowanych funkcji fst i snd, aby uzyskać dostęp do elementów, ale w przypadku krotek z trzema lub więcej elementami, nie można łatwo uzyskać wartości, chyba że dopasujesz wzorzec.

Nawet jeśli zdefiniujesz funkcje niestandardowe, rola każdego elementu jest nadal niejawnie zdefiniowana przez jego porządek porządkowy. Łatwo uzyskać kolejność wartości błędnych, jeśli są tego samego typu.

Jeśli chodzi o modelowanie domen, zawsze preferuję typy jawne, aby było jasne, co się dzieje.

Czy krotki nigdy nie są odpowiednie?

Krotki są przydatne w innych kontekstach. Aby użyć funkcji typu ad-hoc typu ad-hoc, są one bardziej odpowiednie niż rekordy.

Weźmy jako przykład, Seq.zip, która pozwala połączyć dwie sekwencje:

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 

Jak widać na tym przykładzie, krotki to tylko krok w kierunku rzeczywistego celu, jakim jest mapa wartości alfabetyczne. Byłoby niezręcznie, gdybyśmy musieli zdefiniować typ rekordu, gdybyśmy chcieli komponować wartości razem wewnątrz wyrażenia. Krotki są odpowiednie do tego zadania.

+0

"Z kolejnymi zapisami kolejność elementów nie ma znaczenia" ... Czasem ma to znaczenie, np. Gdy instancje rekordu są porównywane lub sortowane, ponieważ rekordy zapewniają domyślnie porównanie strukturalne. –

+1

@MarcSigrist Czy możesz to rozwinąć? Jak pokazałem powyżej, "{Year = 2016; Miesiąc = 1; Dzień = 2} 'jest równe' {Rok = 2016; Dzień = 2; Month = 1} ', nawet jeśli kolejność wyświetlania elementów nie jest taka sama. –

+1

Kolejność elementów ma znaczenie w deklaracji typu record_, ponieważ zależy od niej generowana przez kompilator implementacja IComparable. –

1

Możesz być zainteresowany optional parameters, które są dozwolone tylko dla typów. Aby uniknąć zmiany kolejności argumentów, dopełniłem także nazwy opcjonalnej (prawdopodobnie lepiej modeluje ona również domenę problemów, np. Podczas napotkania Mononyms).

Aby przedstawić różnicę między dwoma punktami w czasie istnieje już biblioteka funkcji w ramach, System.TimeSpan.

Twój Przykładem może być zatem zapisana jako

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) 

Ideą jest zapewnienie podjęcia tupled extension methods że argumenty o różnej długości i powrót złożoną strukturę danych, na przykład rekord lub struktura.

Powiązane problemy