在 F# 模式匹配中拆分代码块以提高可读性

Splitting out blocks of code in F# pattern matching for readability

// 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

在使用 "match" 的模式匹配中,每个模式的代码可能很大,请参阅上面的 Foo,这让我想将块拆分为单独的调用提高可读性。

这个问题可能是即使模式不匹配也会评估调用,如上面的 Bar 所示。

在每个模式下的代码可能很大的情况下,提高可读性的首选方法是什么。或者还有其他明显的解决方案吗?

// ===== 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

一般来说,我尽量让我的代码的语义与我想要完成的事情的逻辑相匹配。对于您的情况,我会将您的问题描述为:

There are two different pieces of simple data I may receive. Based on which one I receive, run a specific piece of code.

这与选项 2 完全对应。我可能会也可能不会像您那样嵌套函数,具体取决于上下文。其他两个选项会导致您的目标与代码不匹配。

选项 1:

我会把这个逻辑描述为:

I have information (or context) now from which I can build two different calculations, one of which may need to be run later. Construct both now, and then evaluate the one which is needed later.

这确实不符合您想要的逻辑,因为上下文或可用数据没有变化。无论哪种方式,您仍然处于相同的上下文中,因此使其成为惰性的额外复杂性只会混淆函数的逻辑。

选项 3:

我会把这个逻辑描述为:

I have some data which may fit into one of two cases. Determining which case holds requires some complex logic, and that logic may also have some overlap with the logic needed to determine the needed return value of the overall function. Move the logic to determine the case into a separate function (an active pattern in this case), and then use the return value of the active pattern to determine the resulting value of the function.

这个将控制流(确定我接下来应该执行什么代码)与在每种情况下执行的逻辑混为一谈。如果两者之间没有重叠,那么将控制流与以下逻辑分开会更加清晰。

因此,请将您的代码结构与您的问题结构相匹配,否则您最终会想知道额外的代码复杂性会带来什么。

我可能只是将模式匹配的两个主体提取到采用 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 的解决方案,但由于我们从来 re-using 惰性值的结果,因此确实没有理由使用惰性值 - 函数更简单且它还延迟了 body.

的评估

如果您随后发现 caseOnecaseTwo 之间有一些共性,您可以再次将其提取到另一个函数中,它们都可以调用。