статические члены против модуля для типа в F #?

Предположим, у меня есть такой тип:

type Season =
| Spring
| Summer
| Autumn
| Winter

Я хочу иметь функцию next которая возвращает следующий сезон:

let next s = 
  match s with
  | Spring -> Summer
  | Summer -> Autumn
  | Autumn -> Winter
  | Winter -> Spring

Есть два места, где я могу поставить эту функцию.

В названном модуле:

module Season = 
  let next s = 
    match s with
    | Spring -> Summer
    | Summer -> Autumn
    | Autumn -> Winter
    | Winter -> Spring

Или как статический член типа:

type Season =
| Spring
| Summer
| Autumn
| Winter
  with 
    static member next s = 
      match s with
      | Spring -> Summer
      | Summer -> Autumn
      | Autumn -> Winter
      | Winter -> Spring

Каковы причины в пользу каждого подхода?

Ответ 1

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

Я склонен отдавать предпочтение статическим членам для функциональности, которая очень "связана" с типом, в большей степени, чем какой-либо конкретный фрагмент кода бизнес-логики. Подумайте о функциях Parse или умных конструкторах/фабричных методах. Общий подход заключается в том, что если бы я реорганизовал код, переместив тип в другое место, это были бы функции, которые я определенно хотел бы переместить вместе с ним. Наличие их в качестве статических членов также помогает обнаружению через intellisense, поскольку вам нужно только знать имя типа, чтобы найти их.

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

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

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

Ответ 2

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