Я изучаю F # и играю с предметным моделированием, используя систему типов.
В моем очень простом примере c, скажем, мы хотим управлять клиентами для отеля. Клиент может находиться в разных состояниях:
- Новый клиент
- Определена контактная информация.
- Клиент принял GPDR.
- Клиент имеет проверено.
Все эти состояния представлены в виде различных типов. Мы также определяем, что клиент находится в состоянии «Ожидание», пока клиент не зарегистрировался, но предоставил контактную информацию и / или принял GPDR:
type CustomerId = CustomerId of Guid
type ContactInformation = ContactInformation of string
type AcceptDate = AcceptDate of DateTime
type CheckInDate = CheckInDate of DateTime
type NewCustomer =
private
{ Id: CustomerId }
type ContactOnlyCustomer =
private
{ Id: CustomerId
Contact: ContactInformation }
type AcceptedGdprCustomer =
private
{ Id: CustomerId
Contact: ContactInformation
AcceptDate: AcceptDate }
type PendingCustomer =
private
| ContactOnly of ContactOnlyCustomer
| AcceptedGdpr of AcceptedGdprCustomer
type CheckedInCustomer =
private
{ Id: CustomerId
Contact: ContactInformation
AcceptDate: AcceptDate
CheckInDate: CheckInDate }
type Customer =
private
| New of NewCustomer
| Pending of PendingCustomer
| CheckedIn of CheckedInCustomer
Теперь я хочу обновить контакт информация для клиента со следующей функцией (независимо от того, в каком «состоянии» находится данный клиент):
let updateContact (customer: Customer) contact =
match customer with
| New c -> ContactOnly { Id = c.Id; Contact = contact }
| Pending pending ->
match pending with
| ContactOnly c -> ContactOnly { c with Contact = contact }
| AcceptedGdpr c -> AcceptedGdpr { c with Contact = contact }
| CheckedIn c -> CheckedIn { c with Contact = contact } // <- Here I get a compile error saying that all branches must return the same type.
Проблема здесь в том, что различные типы возвращаются выражением сопоставления с образцом. Случаи объединения ContactOnly
и AcceptedGdpr
относятся к типу PendingCustomer
, тогда как случаи объединения CheckedIn
относятся к типу Customer
.
Как справиться с таким сценарием? По сути, случай объединения New
должен превратить клиента в клиента ContactOnly
. Все остальные случаи (когда клиент уже определил контактную информацию) должны быть обновлены с использованием новой контактной информации.
Я пытался определить тип Customer
следующим образом, т.е. переместить случаи объединения DetailsOnly
и AcceptedGdpr
непосредственно в тип Customer
:
type Customer =
private
| New of NewCustomer
| ContactOnly of ContactOnlyCustomer
| AcceptedGdpr of AcceptedGdprCustomer
| CheckedIn of CheckedInCustomer
При этом мне не нужно совпадать с вложенным шаблоном:
let updateDetails (customer: Customer) contact =
match customer with
| New c -> ContactOnly { Id = c.Id; Contact = contact }
| ContactOnly c -> ContactOnly { c with Contact = contact }
| AcceptedGdpr c -> AcceptedGdpr { c with Contact = contact }
| CheckedIn c -> CheckedIn { c with Contact = contact }
Это работает , но это не похоже на правильный подход, поскольку это приводит к дублированию определений типов, когда я все еще хочу использовать тип PendingCustomer
также для других функций.
Как начинающий F #, у меня есть чувство что я скучаю по крошечной простой вещи здесь.