如何避免基于长模式匹配的函数?

How to avoid long pattern match based functions?

当使用具有相当多构造函数的联合类型时,我几乎总是发现自己在单个函数中实现大量逻辑,即在一个函数中处理所有情况。有时我想提取单个案例的逻辑来分离函数,但是一个函数不能只接受一个 "constructor" 作为参数。

示例: 假设我们有典型的 "expression" 类型:

type Formula =    
| Operator of OperatorKind * Formula * Formula
| Number of double   
| Function of string * Formula list
[...]

然后,我们要计算表达式:

let rec calculate efValues formula = 
match formula with
    | Number n -> [...] 
    | Operator (kind, lFormula, rFormula) -> [...]
    | [...]

这样的函数会很长,并且会随着每个新的公式构造函数而增长。 我怎样才能避免这种情况并清理此类代码?长模式匹配结构是不可避免的吗?

您可以使用显式元组定义 Formula 联合的 Operator 情况:

type Formula =    
 | Operator of (string * Formula * Formula)
 | Number of double   

如果你这样做,编译器会让你同时使用 Operator(name, left, right) 和使用单个参数 Operator args 进行模式匹配,所以你可以这样写:

let evalOp (name, l, r) = 0.0
let eval f = 
  match f with
 | Number n -> 0.0
 | Operator args -> evalOp args

我会觉得这有点令人困惑,所以在类型定义中更明确一些并使用命名元组(相当于上面的)可能会更好:

type OperatorInfo = string * Formula * Formula
and Formula =    
 | Operator of OperatorInfo
 | Number of double   

或者更明确地使用记录:

type OperatorInfo = 
 { Name : string
   Left : Formula 
   Right : Formula }
and Formula =    
 | Operator of OperatorInfo
 | Number of double   

然后您可以使用以下之一进行模式匹配:

| Operator args -> (...)
| Operator { Name = n; Left = l; Right = r } -> (...)

我会说您通常希望在一个函数中处理所有情况。这是工会的主要卖点——他们迫使你以这种或那种方式处理所有案件。也就是说,我可以看到你来自哪里。

如果我有一个大联合并且只关心一个案例,我会这样处理,将结果包装在一个选项中:

 let doSomethingForOneCase (form: Formula) =
     match form with
     | Formula (op, l, r) -> 
         let result = (...)
         Some result
     | _ -> None

然后在调用站点以任何合适的方式处理 None。

请注意,这符合 partial active patterns 要求的签名,因此如果您决定需要将此函数用作另一个匹配表达式中的 case,您可以轻松地将其包装在一个活动中模式以获得漂亮的语法。