2016-01-18 2 views
6

Я пытаюсь практиковать Domain Driven Design в F # и наткнулся на следующий вопрос:Когда я должен использовать запись над кортежем?

Почему бы использовать запись при использовании кортежа, как представляется, требуют меньшего синтаксиса и, как представляется, более мощным в отношении сопоставления с образцом и просто общие сценарии использования?

Например, мне кажется, что мне больше не нужно создавать тип записи, чтобы реализовать дискриминационный союз, если вместо этого я должен использовать кортеж.

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) 

Являются ли мои мысли точными?

Являются ли кортежи желательными для записей в большинстве сценариев?

+1

Я бы ответил, но я знаю, что Томас будет делать лучше. IIRC он больше связан с работой с C# и .NET. Если вы чисто в F #, то идите с кортежем. Если вы работаете с другим кодом .NET, переходите к записи. Существуют и другие факторы, например, если они идут с записью, если они не публично идут с кортежем и т. Д. Также перед тем, как вы спросите, также см. [Когда использовать классы, союзы, записи и структуры] (https: // msdn.microsoft.com/en-us/library/dd233205.aspx) в конце ссылки. –

+1

Вы видели всю информацию в теге [F #] (http://stackoverflow.com/tags/f%23/info)? –

+0

[Руководство по дизайну компонентов F #] (http://fsharp.org/specs/component-design-guidelines/) –

ответ

12

Для моделирования доменов я бы рекомендовал использовать типы с именованными элементами; то есть записи, дискриминационный союз и, возможно, случайный класс или интерфейс.

Структурно, записи и кортежи схожи; в языке алгебраических данных, они оба являются типами продуктов.

Разница в том, что с кортежами порядок значений имеет значение, а роль каждого элемента является неявной.

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

В приведенном выше примере вы можете догадаться, что эти кортежи модели датируются, но какие именно? Это второе января 2016 года? Или это первое февраля 2016 года?

с записями, с другой стороны, порядок элементов не имеет значения, потому что вы связываете их, и доступ к ним по имени:

> 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 

Это также ясно, когда вы хотите, чтобы вытащить составляющие значения. Вы можете легко получить год от рекордного значения:

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

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

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

Это гораздо труднее вытащить значения из кортежа:

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

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

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

val y : int = 2016 

Предоставленные, для пар, которые вы можете использовать встроенные функции fst и snd для доступа к элементам, но для кортежей с тремя или более элементами вы не можете легко получить значения, если вы не сопоставляете шаблон.

Даже если вы определяете пользовательские функции, роль каждого элемента по-прежнему неявно определяется его порядковым номером. Легко получить неправильный порядок значений, если они одного типа.

Итак, для моделирования доменов я всегда предпочитаю явные типы, так что ясно, что происходит.

Корзины никогда не подходят, то?

Кортежи полезны в других контекстах. Если вам нужно использовать ad hoc тип для составления функций, они более подходят, чем записи.

Рассмотрим, в качестве примера, Seq.zip, который позволяет комбинировать две последовательности:

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 

Как вы можете видеть в этом примере, кортежи являются лишь шагом на пути к реальной цели, которая является карта алфавитные значения. Было бы неудобно, если бы нам пришлось определять тип записи, когда мы хотели составлять значения вместе внутри выражения. Кортежи хорошо подходят для этой задачи.

+0

«С учетными записями, с другой стороны, порядок элементов не имеет значения» ... Иногда это имеет значение, например, когда экземпляры записи сравниваются или сортируются, поскольку записи обеспечивают структурное сравнение по умолчанию. –

+1

@MarcSigrist Можете ли вы подробно остановиться на этом? Как я показал выше, '{Year = 2016; Месяц = ​​1; Day = 2} 'равно' {Year = 2016; День = 2; Month = 1} ', даже если порядок, в котором перечислены элементы, не совпадает. –

+1

Порядок элементов имеет значение _ в объявлении типа записи_, потому что от него зависит реализация компилятора IComparable. –

1

Возможно, вас заинтересует optional parameters, допускается только по типу. Чтобы избежать изменения порядка ваших аргументов, я также добавил факультативное имя (возможно, оно также улучшит вашу проблемную область, например, при встрече Mononyms).

Чтобы представить разницу между двумя моментами времени, в рамке уже есть функция библиотеки, System.TimeSpan.

Ваш пример может поэтому быть записана в виде

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) 

Идея заключается в предоставлении extension methods, которые принимают tupled аргументы различной длины и возвращает сложную структуру данных, например, запись или структура.

Смежные вопросы