在 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

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

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

  • 选项1:懒惰的评估。
  • 选项 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

我可能只是将模式匹配的两个主体提取到需要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的解决方案,但由于我们从不重用惰性值的结果,因此实际上没有理由使用惰性值 - 函数更简单,并且还会延迟对主体的评估。

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

一般来说,我尝试让我的代码语义与我想要完成的逻辑相匹配。 在您的情况下,我会将您的问题描述为:

我可能会收到两种不同的简单数据。 基于哪个 我收到的一个,运行一段特定的代码。

这完全映射到选项 2。 我可能会也可能不会像您那样嵌套函数,具体取决于上下文。 另外两个选项会在您的目标和代码之间造成不匹配。

选项 1:

我将这个逻辑描述为:

我现在有了信息(或上下文(,从中可以构建两个不同的计算,其中一个可能需要稍后运行。现在构建两者,然后评估以后需要的那个。

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

选项 3:

我将这个逻辑描述为:

我有一些数据可能适合两种情况之一。 确定哪种情况成立需要一些复杂的逻辑,并且该逻辑也可能与确定整个函数所需的返回值所需的逻辑有一些重叠。 将用于确定事例的逻辑移动到单独的函数(在本例中为活动模式(,然后使用活动模式的返回值来确定函数的结果值。

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

因此,将代码的结构与问题的结构相匹配,否则人们最终会想知道额外的代码复杂性完成了什么。

最新更新