5

我是 F# 的新手,对整个模式匹配的想法不太熟悉。我试图为我的问题寻找更好的解决方案,但我担心我什至无法正确表达问题——我希望问题标题至少有点准确。

我想要做的是从中提取 2 个“参数” listMethodlistMethod是具有字符串和Expression“参数”的几种类型之一(我怀疑参数是错误的术语):

    let (varDecl, listExpr) =
        match listMethod with 
        | Select (var, expr)  -> (var, expr)
        | Where (var, expr)   -> (var, expr)
        | Sum (var, expr)     -> (var, expr)
        | Concat (var, expr)  -> (var, expr)

然后我继续使用varDecl并最终使用与实际 listMethod 代码类似的匹配表达式,该代码使用了我基于varDecl.

我现在的问题是:我怎样才能使上面的代码更紧凑?

我想匹配所有具有 2 个参数(类型stringExpression)的类型而不自己列出它们,这有点难看且难以维护。

ListMethod类型声明如下(整个就是一个 FsLex/FsYacc 项目):

type ListMethod =
    | Select of string * Expr
    | Where of string * Expr
    | Sum of string * Expr
    | Concat of string * Expr
    | ...
    | somethingElse of Expr

(到目前为止,我只有 form 的类型string * Expr,但这会改变)。

我认为对于任何有经验的人来说,这都是一个相当愚蠢的问题,但正如我所说,我是 F# 的新手,自己找不到解决方案。

提前致谢!

编辑: 我真的很想避免列出所有可能的类型listMethod两次。如果我无法在match表达式中使用通配符或占位符,也许我可以修改listMethod类型以使事情更清晰。

想到的一个选项是只创建一种类型,listMethod并为具体类型创建第三个参数(Select、Where、Sum)。还是有更好的方法?

4

4 回答 4

9

这可能是标准方式:

let (varDecl, listExpr) =
    match listMethod with 
    | Select (var, expr)
    | Where (var, expr)
    | Sum (var, expr)
    | Concat (var, expr) -> (var, expr)

|符号表示,因此or如果其中之一匹配,则将返回结果。只需确保每个案例都具有完全相同的名称(和类型)。

正如 Chuck 评论的那样,这是一个更好的解决方案:

let (Select (varDecl, expr)
    | Where (varDecl, expr)
    | Sum (varDecl, expr)
    | Concat (varDecl, expr)) = listMethod
于 2011-08-30T08:00:06.473 回答
6

我认为对于任何有经验的人来说,这都是一个相当愚蠢的问题,但正如我所说,我是 F# 的新手,自己找不到解决方案。

相反,这是一个非常好的问题,实际上相对来说还比较少见,因为 F# 在这方面不同于其他语言(例如,您可以使用 OCaml 中的多态变体来解决这个问题)。

正如 Ankur 所写,最好的解决方案总是改变你的数据结构,以便在可能的情况下更容易地做你需要做的事情。KVB 使用主动模式的解决方案不仅有价值而且新颖,因为这种语言特性在其他语言中并不常见。Ramon 建议使用或模式组合匹配案例也很好,但您不想编写不完整的模式匹配。

在实践中,这个问题最常见的例子可能是在运算符中:

type expr =
  | Add of expr * expr
  | Sub of expr * expr
  | Mul of expr * expr
  | Div of expr * expr
  | Pow of expr * expr
  | ...

您可以在其中重组您的类型,如下所示:

type binOp = Add | Sub | Mul | Div | Pow

type expr =
  | BinOp of binOp * expr * expr
  | ...

然后像提取子表达式这样的任务:

let subExprs = function
  | Add(f, g)
  | Sub(f, g)
  | Mul(f, g)
  | Div(f, g)
  | Pow(f, g) -> [f; g]
  | ...

可以更轻松地执行:

let subExprs = function
  | BinOp(_, f, g) -> [f; g]
  | ...

最后,不要忘记您可以使用 OOP 构造(例如实现共享接口)来扩充 F# 类型(例如联合类型)。这也可以用来表达共性,例如,如果您对两种类型有两个重叠的要求,那么您可以让它们都实现相同的接口以公开这种共性。

于 2011-08-30T14:49:45.730 回答
4

如果您可以对数据结构进行调整,那么下面的内容将简化模式匹配。

type ListOperations = 
    Select | Where | Sum | Concat


type ListMethod =
    | ListOp of ListOperations * string * Expr
    | SomethingElse of int

let test t = 
    match t with
    | ListOp (a,b,c) -> (b,c)
    | _ -> ....

设计数据结构时应牢记您要对其执行的操作。

于 2011-08-30T10:57:00.693 回答
3

如果有时您希望以相同的方式处理所有案例,而有时您希望根据是否处理 a SelectWhereSum等来区别对待它们,那么一种解决方案是使用活动模式:

let (|OperatorExpression|_|) = function
| Select(var, expr) -> Some(Select, var, expr)
| Where (var, expr) -> Some(Where, var, expr)
| Sum (var, expr) -> Some(Sum, var, expr)
| Concat (var, expr) -> Some(Concat, var, expr)
| _ -> None

现在,如果您需要单独处理案例,您仍然可以正常匹配,但您也可以使用活动模式进行匹配:

let varDecl, listExp = 
    match listMethod with
    | OperatorExpression(_, v, e) -> v, e
    | _ -> // whatever you do for other cases...
于 2011-08-30T13:02:00.133 回答