【问题标题】:Splitting out blocks of code in F# pattern matching for readability在 F# 模式匹配中拆分代码块以提高可读性
【发布时间】:2017-07-23 11:08:55
【问题描述】:
// 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,这让我想将块拆分为单独的调用以提高可读性。

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

  • 选项 1:惰性评估。
  • 选项 2:转发参数。
  • 选项 3:转发参数并使用 Active 模式。

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

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

【问题讨论】:

  • 我会使用主动模式,如果命名合理的话,它们非常擅长解决这类问题。
  • 假设不同的情况应该有不同的代码,我只会使用函数而不是值或`lazy.如果代码相同,可以使用活动模式将不同的情况合并为一个匹配项

标签: f# pattern-matching lazy-evaluation code-readability active-pattern


【解决方案1】:

我可能只是将模式匹配的两个主体提取到采用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】:

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

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

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

    选项 1:

    我将这个逻辑描述为:

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

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

    选项 3:

    我将这个逻辑描述为:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-08-29
      • 2020-11-18
      • 2016-02-19
      • 2011-05-02
      • 2015-01-25
      • 2010-10-07
      • 2022-06-19
      相关资源
      最近更新 更多