【问题标题】:F# optional pattern matchingF# 可选模式匹配
【发布时间】:2015-01-25 13:03:21
【问题描述】:

现在我有以下模式匹配

let argList = args |> List.ofSeq
match argList with
| "aaa" :: [] -> run "aaa"
| "bbb" :: DateTimeExact "yyyyMMdd" date :: [] -> run "bbb" date
....

用于解析命令行参数如

执行 aaa

执行 bbb 20141101

现在我希望能够添加选项-o(可选)。如exec bbb 20141101 -o。如何修改模式来做到这一点?更好的是,-o 应该可以放在任何位置。

【问题讨论】:

  • 先对args进行排序,然后对排序后的列表进行模式匹配怎么样?
  • 好主意。可选匹配怎么样?
  • 题外话,对于一个真实的项目,考虑使用一些库解决方案来解析命令行。有很多,从quite simple ones 到复杂的(1)(2)

标签: f#


【解决方案1】:

如果您正在寻找一种可以轻松扩展的方法,我发现在解释命令行参数时使用 F# 类型系统真的很有帮助。

最终,您希望获得 argList 中提供的详细信息。所以首先,定义你想要的类型。然后,您将字符串列表解析为这些类型。在您的示例中,您似乎可以使用“aaa”或“bbb”,然后紧跟日期。感觉就像是 Union 类型:

type Cmd = 
    | Aaa 
    | Bbb of DateTime

另外,可能有一个额外的标志通过“-o”可以出现在任何时候(除了 bbb 和它的日期之间)。所以感觉就像一个 Record 类型:

type Args = { Cmd: Cmd; OSet: bool; }

所以现在我们知道我们想要将 argList 集合变成 Args 的一个实例。

您想要遍历 argList 中的每个项目并处理它们。一种方法是使用递归。在继续检查列表的其余部分之前,您希望匹配每个项目(或一对项目,或三倍等)。在每场比赛中,您都会更新* Args 以包含新的详细信息。

(* 因为 Args 是不可变的,所以您实际上并没有更新它。创建了一个新实例。)

let rec parseArgs' argList (a: Args) =
    match argList with
    | [] -> 
        a
    | "aaa"::xs -> 
        parseArgs' xs { a with Cmd = Aaa }
    | "bbb":: DateTimeExact "yyyyMMdd" d ::xs -> 
        parseArgs' xs { a with Cmd = Bbb(d) }
    | "-o"::xs -> 
        parseArgs' xs { a with OSet = true }
    | x::_ -> 
        failwith "Invalid argument %s" x

调用 parseArgs' 时,您需要提供 Args 的初始版本 - 这是您的“默认”值。

let parseArgs argList = parseArgs' argList { Cmd = Aaa; OSet = false }

然后你就可以使用它了:

parseArgs ["aaa"] // valid
parseArgs ["bbb"; "20141127"] // valid
parseArgs ["bbb"; "20141127";"-o"] // valid
parseArgs ["-o";"bbb"; "20141127"] // valid
parseArgs ["ccc"] // exception

您的 Args 实例现在以强类型形式包含所有详细信息。

let { Cmd = c; OSet = o } = parseArgs ["aaa"]

match c with
| Aaa -> run "aaa" o
| Bbb d -> run "bbb" d o

如果您想要更多不同的选项,您只需将它们添加到您的类型中,然后更新您的匹配语句以处理任何输入版本。

当然有很多不同的方法来处理这个问题。您可能是错误消息而不是异常。您可能希望 Cmd 值是一个选项,而不是默认为“Aaa”。等等等等。


根据评论进行编辑:

添加一个额外的-p someValue 很简单。

首先,更新Args 以保存新数据。在您的示例中,“someValue”的值是重要的部分,但可能会或可能不会提供。它由“-p”定义,以便我们看到它时就知道它。为简单起见,我将假设 someValue 是string。所以Args变成了:

type Args = { Cmd: Cmd; OSet: bool; PValue: string option }

Args 中添加新字段后,您的“默认”应该会报错,因为未设置新字段。我要说的是,默认情况下,-p 没有设置(这就是我使用string option 的原因)。所以更新到:

let parseArgs argList = parseArgs' argList { Cmd = Aaa; OSet = false; PValue = None }

然后,您只需要在提供价值时识别并捕获它。这是模式匹配部分:

let rec parseArgs' argList (a: Args) =
    match argList with
    ... snip ...
    | "-p"::p::xs ->
        parseArgs' xs { a with PValue = Some p}
    ... snip ...

然后,当您拥有 Args 项目时,只需使用 PValue 值。

注意:我在这里没有做太多验证 - 我只是假设“-p”之后的任何内容都是我想要的值。您可以在模式匹配期间使用“when”保护添加验证,或者在创建 Args 值后对其进行验证。你走多远取决于你。我通常会考虑我的听众(只是我、同事、整个互联网)。

是否允许单独指定“bbb”和日期是否是一个好主意取决于您作为设计者,但如果日期应该只用“bbb”命令指定并且是强制性的,那么将它们放在一起可能是有意义的。

如果 Cmd 和日期没有“紧密联系”,您可以将日期与 cmd 分开进行模式匹配。如果是这样,您可以将日期从 Cmd 联合中的保留日期移到 Args 中的一个字段。

如果日期是可选的,您可以改用-d [date] 选项。这将保持与其他可选参数相同的模式。

一个重要的目标是尝试使您的界面尽可能直观,以供人们使用。这主要是关于“可预测”。这并不一定意味着要满足尽可能多的输入样式。

【讨论】:

  • 是否也可以使bbb -o 20141127 合法?另一个-p someValue怎么样?
猜你喜欢
  • 2020-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-21
  • 1970-01-01
  • 1970-01-01
  • 2014-12-22
相关资源
最近更新 更多