F#可选模式匹配

时间:2014-11-27 02:19:05

标签: f#

现在我有以下模式匹配

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

它用于解析命令行参数,如

  

exec aaa

     

exec bbb 20141101

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

1 个答案:

答案 0 :(得分:5)

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

最终,您希望最终了解argList中提供的内容的详细信息。首先,定义您想要的类型。然后,您将字符串列表解析为这些类型。在您的示例中,您似乎可以拥有" aaa"或" bbb"紧接着是一个约会。这感觉就像一个联盟类型:

type Cmd = 
    | Aaa 
    | Bbb of DateTime

此外,可能会有一个额外的标志传递" -o"可以出现在任何一点(除了bbb和它的日期之间)。所以感觉就像一个记录类型:

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"之后发生了什么。是我想要的价值。你可以在模式匹配期间使用"当"保护,或在创建后验证Args值。你走多远取决于你。我通常会想到我的观众(只有我与工作同事和整个互联网)。

允许" bbb"是否是一个好主意。并且单独指定的日期取决于您作为设计者,但是如果日期仅应使用" bbb"来指定。命令并且是必需的,然后将它们保持在一起可能是有意义的。

如果Cmd和日期不紧密相关"您可以将日期与cmd分开进行模式匹配。如果是这样,您可以将日期从Cmd联盟上移到Args中自己的字段。

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

一个重要的目标是尝试让您的界面尽可能直观,供人们使用。这主要是关于"可预测的"。它并不一定意味着迎合尽可能多的输入风格。