Анонимный тип записи внутри размеченного объединения

Учебник по F# включает следующий фрагмент:

/// A record for a person's first and last name
type Person = {     
    First : string
    Last  : string
}

/// define a discriminated union of 3 different kinds of employees
type Employee = 
    | Engineer  of Person
    | Manager   of Person * list<Employee>            // manager has list of reports
    | Executive of Person * list<Employee> * Employee // executive also has an assistant

Тот факт, что Manager и Executive описываются как кортежи, оскорбляет мои чувства (меня легко обидеть). Мне кажется, что это не очень выразительно. Я попытался изменить их следующим образом:

/// define a discriminated union of 3 different kinds of employees
type Employee = 
    | Engineer  of Person
    | Manager   of { Name: Person; Reports: Employee list }            // manager has list of reports
    | Executive of { Name: Person; Reports: Employee list; Assistant: Employee}   // executive also has an assistant

К сожалению, определения Manager и Executive теперь выдают ошибку: «Эта конструкция устарела; рассмотрите возможность использования вместо нее отдельного типа записи». Хорошо, кажется справедливым, давайте назовем его ManagerType. Но подождите... ManagerType относится к Employee (для части Reports), а Employee относится к ManagerType (для опции Manager).

Есть ли здесь решение? Разве две структуры данных не могут быть определены в терминах друг друга?


person CSJ    schedule 05.02.2014    source источник


Ответы (4)


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

    type Employee = 
    | Engineer  of Person
    | Manager   of Manager            // manager has list of reports
    | Executive of Executive
    and Manager = { Name: Person; Reports: Employee list }
    and Executive = { Name: Person; Reports: Employee list; Assistant: Employee }
person Lee    schedule 05.02.2014
comment
Интересный. Можем ли мы также использовать наследование? Может ли Executive быть производным от Manager? Или это работает только с записями? - person Phillip Scott Givens; 06.02.2014

Если вы используете F# v3.1, вы можете использовать именованные поля объединения [ MSDN] (и защитите свои деликатные чувства):

type Employee = 
    | Engineer  of Person
    | Manager   of Name: Person * Reports: Employee list
    | Executive of Name: Person * Reports: Employee list * Assistant: Employee
person Daniel    schedule 05.02.2014
comment
Это решение неплохое (оно позволяет не беспокоить мою слабую конституцию), но как бы оно выглядело в выражении сопоставления с образцом, если бы я хотел извлечь только отчеты, скажем, из менеджера? Используя другие решения, это будет: | Manager ({Reports=rep}) -> whatever. - person CSJ; 06.02.2014
comment
У меня не установлена ​​версия 3.1, но, согласно странице MSDN, указанной в моем ответе, это будет | Manager (Reports=rep) -> .... - person Daniel; 06.02.2014
comment
Это будет выглядеть как этот менеджер (человек, отчет) с той лишь разницей, что вы можете назвать их - person kam; 14.11.2018

Взаимно-рекурсивные типы объявляются с помощью type ... = ... and ... = ...:

type Employee = 
    | Engineer  of Person
    | Manager   of Manager 
    | Executive of Executive
and Manager = { Name: Person; Reports: Employee list }
and Executive = { Name: Person; Reports: Employee list; Assistant: Employee}
person kvb    schedule 05.02.2014
comment
Извини; Ли пробрался немного быстрее :) - person CSJ; 06.02.2014

Начиная с FSharp 4.6 вы можете использовать Анонимные записи с объединениями. Итак, с вашим примером можно определить таким образом:

type Person = {     
    First : string
    Last  : string
}

type Employee =
    | Engineer of Person
    | Manager of {| Name: Person; Reports: Employee list |}
    | Executive of {| Name: Person; Reports: Employee list; Assistant: Employee |}
person Jono Job    schedule 15.07.2019
comment
Для полноты: анонимные записи не могут быть сопоставлены с образцом (пока). - person Good Night Nerd Pride; 23.12.2019