Когда использовать дискриминацию объединения и типа записи в F#

Я пытаюсь понять основы F#, прежде чем переходить к сложным примерам. В материале, который я изучаю, представлены как типы Discriminate Unions, так и Record. Я просмотрел материал для обоих, но мне все еще неясно, почему мы должны использовать один вместо другого.

Большинство примеров игрушек, которые я создал, кажутся реализуемыми в обоих случаях. Записи кажутся очень близкими к тому, что я считаю объектом в C#, но я стараюсь не полагаться на сопоставление с C# как на способ понять F#.

So...

  • Есть ли четкая причина использовать один над другим?

  • Существуют ли определенные канонические случаи, когда они применяются?

  • Доступны ли определенные функции в одном, но нет в другом?


person Chris Tarn    schedule 25.06.2013    source источник
comment
На этой странице есть краткий абзац msdn.microsoft.com/en-us/library/ dd233205.aspx в конце.   -  person John Palmer    schedule 25.06.2013


Ответы (4)


Подумайте об этом, как о записи «и», а о разделенном союзе — «или». Это строка и целое число:

type MyRecord = { myString: string
                  myInt: int }

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

type MyUnion = | Int of int
               | Str of string

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

type Game =
  | Title
  | Ingame of Player * Score * Turn
  | Endgame of Score
person Robert Jeppesen    schedule 25.06.2013
comment
Итак, в DU есть ли способ создать комбинированный тип, расширяющий Game? Например | InGameTitle of Title * В игре. то есть кортеж, содержащий Название * Игрок * Счет * Ход - person Chris Tarn; 25.06.2013
comment
@Chris: Возникает только вопрос: зачем тебе хотеть? - person ildjarn; 26.06.2013
comment
@ildjarn Вы совершенно правы. Когда я впервые написал этот комментарий, у меня не было полного понимания целей DU. Ответ MisterMetaphor помог мне понять, почему вы будете использовать его так, как это сделал Роберт, а не так, как я описал. - person Chris Tarn; 26.06.2013

Используйте записи (называемые типами продуктов в теории функционального программирования) для сложных данных, которые описываются несколькими свойствами, такими как запись базы данных или какой-либо объект модели:

type User = { Username : string; IsActive : bool }

type Body = { 
    Position : Vector2<double<m>>
    Mass : double<kg>
    Velocity : Vector2<double<m/s>> 
}

Используйте размеченные объединения (называемые типами сумм) для данных, возможные значения которых можно перечислить. Например:

type NatNumber =
| One
| Two
| Three
...

type UserStatus =
| Inactive
| Active
| Disabled

type OperationResult<'T> =
| Success of 'T
| Failure of string

Обратите внимание, что возможные значения для размеченного значения объединения также являются взаимоисключающими — результатом операции может быть либо Success, либо Failure, но не оба одновременно.

Вы можете использовать тип записи для кодирования результата операции, например:

type OperationResult<'T> = { 
    HasSucceeded : bool
    ResultValue : 'T
    ErrorMessage : string
}

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

match result with
| Success resultValue -> ...
| Failure errorMessage -> ...

И если вы сопоставите шаблон с версией типа записи нашего типа операции, это будет иметь меньше смысла:

match result with
| { HasSucceeded = true; ResultValue = resultValue; ErrorMessage = _ } -> ...
| { HasSucceeded = false; ErrorMessage = errorMessage; ResultValue = _ } -> ...

Это выглядит многословно и неуклюже, и, вероятно, менее эффективно. Я думаю, когда у вас возникает такое чувство, это, вероятно, намек на то, что вы используете неправильный инструмент для задачи.

person MisterMetaphor    schedule 25.06.2013
comment
Спасибо за этот ответ. Теперь я вижу, где DU имеет особый смысл. - person Chris Tarn; 25.06.2013

Если вы работаете с C#, вы можете понимать записи как запечатанные классы с добавленными значениями:

  • Неизменяемый по умолчанию
  • Структурное равенство по умолчанию
  • Легко подобрать шаблон
  • и т.п.

Размеченные союзы кодируют альтернативы, например.

type Expr =
    | Num of int
    | Var of int 
    | Add of Expr * Expr 
    | Sub of Expr * Expr

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

Вам нужны все поля для создания записи. Вы также можете использовать DU внутри записей и наоборот.

type Name =
    { FirstName : string;
      MiddleName : string option;
      LastName : string }

В приведенном выше примере показано, что отчество является необязательным.

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

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

person pad    schedule 25.06.2013
comment
Спасибо. И этот ответ, и другой указывают на тот факт, что DU является своего рода отношением ИЛИ. Но, как вы понимаете, один DU может содержать несколько значений. то есть type Name может иметь значение для FirstName , MiddleName, and LastName . Это все еще оставляет меня немного неуверенным в том, в чем разница между записью, которая имеет значения для всех полей, и DU, которая имеет значения для всех полей. Может ли DU делать какие-то выводы или операции, недоступные записи? Или здесь неизменяемое свойство? - person Chris Tarn; 25.06.2013

Один (слегка ошибочный) способ понять DU — рассматривать его как причудливый «объединение» C#, в то время как запись больше похожа на обычный объект (с несколькими независимыми полями).

Другой способ взглянуть на DU — рассматривать DU как двухуровневую иерархию классов, где верхний тип DU — это абстрактный базовый класс, а случаи DU — это подклассы. Это представление на самом деле близко к фактической реализации .NET, хотя эта деталь скрыта компилятором.

person Luc C    schedule 25.06.2013
comment
Одно важное отличие от объектно-ориентированной иерархии наследования состоит в том, что разные случаи DU являются просто тегами, а не разными (под)типами. Это иногда смущает новичков. - person Frank; 25.06.2013