Разделение блоков кода в F# сопоставлении с образцом для удобства чтения

// Standard pattern matching.
let Foo x =
  match x with
  | 1 ->
      // ... lots of code, only evaluated if x == 1
  | 2 ->
      // ... lots of code, only evaluated if x == 2

// Standard pattern matching separated out, causing exception.
let Bar x =
  let valueOne = //... lots of code, evaluated always. Exception if value <> 1.
  let valueTwo = //... lots of code, evaluated always. Exception if value <> 2.

  match x with
  | 1 -> valueOne
  | 2 -> valueTwo

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

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

  • Вариант 1: ленивый eval.
  • Вариант 2: переслать аргумент / параметры.
  • Вариант 3: переслать аргумент / параметры и использовать активный шаблон.

Каков предпочтительный метод улучшения читабельности, когда код под каждым шаблоном может быть большим. Или есть другое очевидное решение проблемы?

// ===== OPTION 1 =====
// Pattern matching separated out, lazy eval.
let Foo x =
  let valueOne = lazy //... lots of code, evaluated on Force().
  let valueTwo = lazy //... lots of code, evaluated on Force().

  match x with
  | 1 -> valueOne.Force()
  | 2 -> valueTwo.Force()

// ===== OPTION 2 =====
// Pattern matching separated out, with arguments.
let Foo x =
  let valueOne a = //... lots of code.
  let valueTwo a = //... lots of code.

  match x with
  | 1 -> valueOne x
  | 2 -> valueTwo x

// ===== OPTION 3 =====
// Active Pattern matching separated out, with arguments.
let Foo x = 
  let (|ValueOne|_|) inp =
    if inp = 1 then Some(...) else None

  let (|ValueTwo|_|) inp =
    if inp = 2 then Some(...) else None

  match x with
  | ValueOne a -> a
  | ValueTwo b -> b

2 ответа

Решение

Я бы, вероятно, просто выделил два тела сопоставления с образцом в функции, которые принимают unit:

let caseOne () = 
  // Lots of code when x=1

let caseTwo () = 
  // Lots of code when x=2

let Foo x =
  match x with
  | 1 -> caseOne()
  | 2 -> caseTwo()

Это похоже на ваше решение с использованием lazy, но так как мы никогда не используем результат lazy-значения повторно, на самом деле нет смысла использовать lazy-значения - функция проще и также задерживает оценку тела.

Если вы обнаружите, что между caseOne а также caseTwoВы можете снова извлечь это в другую функцию, которую они оба могут вызвать.

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

Есть две разные части простых данных, которые я могу получить. Основываясь на том, что я получаю, запустите определенный кусок кода.

Это точно соответствует варианту 2. Я могу или не могу вкладывать функции, которые у вас есть, в зависимости от контекста. Два других варианта создают несоответствие между вашей целью и вашим кодом.

Опция 1:

Я бы описал логику этого как:

Теперь у меня есть информация (или контекст), из которой я могу построить два разных вычисления, один из которых, возможно, потребуется выполнить позже. Построим оба сейчас, а потом оценим тот, который нужен позже.

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

Вариант 3:

Я бы описал логику этого как:

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

Это связывает поток управления (определяющий, какой код я должен выполнить следующим) с логикой, выполняемой в каждом случае. Гораздо яснее отделить поток управления от следующей логики, если нет перекрытия между ними.

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

Другие вопросы по тегам