Рекурсивный тип объединения и список

#f#

#f#

Вопрос:

Я начинаю с F #, и я читал о рекурсивных типах объединения на https://fsharpforfunandprofit.com/posts/fsharp-in-60-seconds //

Приведен пример из ссылки

 type Employee =
  | Worker of Person
  | Manager of Employee list
  

Как я могу создать значение типа Manager и назначить ему список рабочих?

Я могу создать значение типа Worker , подобное этому:

 let john = {First = "John"; Last="Doe"}
let worker = Worker john
  

Но я не знаю, как создать значение jane типа Manager john , присвоенное в качестве элемента Empoyee списка:

 let jane =  { First="Jane"; Last="Doe" }
let manager = Manager [worker]
  

Это работает, но я не могу назначить First и Last от jane до manager .

Предполагается First Last ли, что в этом примере у менеджера вообще нет /?

Как мне изменить определение типа для Manager, чтобы иметь оба списка Employee ?

Кроме того, похоже, что у него также Manager нет члена employees , к которому я могу получить доступ после создания значения типа Manager ?

Ответ №1:

Учитывая это определение типа, менеджер не может иметь имя / фамилию. Определение типа не включает ничего подобного.

Чтобы Manager работать аналогично Worker в этом отношении, просто дайте ему Person значение:

 type Employee = Worker of Person | Manager of Person * Employee list
  

Затем вы можете создать менеджера Джейн:

 let manager = Manager ({ First = "Jane"; Last = "Doe" }, [worker])
  

Что касается члена employees , есть несколько вещей, которые нужно сказать.

Во-первых, держитесь подальше от членов. F # является первым функциональным. Функции лучше, чем члены (по причинам, в которые я не собираюсь здесь вдаваться). Есть функция:

 let employees emp = 
    match emp with
    | Manager (_, emps) -> emps
    | Worker _ -> ???  // what to return if the employee turned out to be worker?
  

И сразу же вы можете увидеть трудность: когда сотрудником является a Manager , конечно, мы можем вернуть их сотрудников. Но что, если это a Worker ? Что тогда возвращать?

Одним из разумных предложений было бы вернуть пустой список, потому что, в конце концов, у a Worker нет сотрудников, работающих на них, верно?

 let employees emp = 
    match emp with
    | Manager (_, emps) -> emps
    | Worker _ -> []
  

Другой возможностью было бы вернуть an Option . Какой подход является «правильным», зависит от того, что вы собираетесь делать с результатом, поэтому только вы можете ответить на этот вопрос.

Но если вы абсолютно настаиваете на том, чтобы иметь участника (и, пожалуйста, пожалуйста, тщательно подумайте, зачем вам это нужно), вы определенно можете добавить участника в свой тип следующим образом:

 type Employee = Worker of Person | Manager of Person * Employee list
    with
        member this.employees =
          match this with
          | Manager (_, emps) -> emps
          | Worker _ -> []
  

Комментарии:

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

2. @CharlesRoddie на самом деле, два подхода не являются взаимоисключающими, и вы можете использовать оба (как и сам F #, где вы можете использовать, например List.length xs , или xs.Length ).