18

我正在尝试解析 F# 应用程序中的命令行参数。我在参数列表上使用模式匹配来完成它。就像是:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | "/out" :: fileName :: rest -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }

问题是我想让"/out"匹配大小写不敏感,同时保留其他内容的大小写。这意味着我无法更改输入并将输入的小写版本与之匹配(这将丢失fileName大小写信息)。

我想了几个解决方案:

  • 诉诸when不太理想的条款。
  • 每次匹配一个元组,第一个将是实际参数(我将保存它以供进一步处理并将通配符匹配它),第二个将是此类匹配中使用的小写版本。这看起来比第一个更糟糕。
  • 使用主动模式,但这看起来太冗长了。我将不得不重复ToLower "/out"每个项目之前的内容。

做这些事情有更好的选择/模式吗?我认为这是一个普遍的问题,应该有一个很好的方法来处理它。

4

4 回答 4

32

我非常喜欢你使用 F# 活动模式来解决这个问题的想法。它比使用预处理更冗长,但我认为它非常优雅。此外,根据一些 BCL 指南,您不应该ToLower在比较字符串时使用(忽略大小写)。正确的做法是使用OrdinalIgnoreCase标志。您仍然可以定义一个很好的活动模式来为您执行此操作:

open System

let (|InvariantEqual|_|) (str:string) arg = 
  if String.Compare(str, arg, StringComparison.OrdinalIgnoreCase) = 0
    then Some() else None

match "HellO" with
| InvariantEqual "hello" -> printfn "yep!"
| _ -> printfn "Nop!"    

你说得对,它更冗长,但它很好地隐藏了逻辑,它给你足够的能力来使用推荐的编码风格(我不确定如何使用预处理来完成)。

于 2010-02-09T02:34:57.047 回答
2

我可能会做一些预处理以允许在关键字开头使用“-”或“/”,并规范大小写:

let normalize (arg:string) =
    if arg.[0] = '/' || arg.[0] = '-' then 
        ("-" + arg.[1..].ToLower())
    else arg
let normalized = args |> List.map normalize

这可能并不理想,但并不是任何用户都会有足够的耐心来输入如此多的命令行参数,以至于在它们之间循环两次显然很慢。

于 2010-02-08T22:51:42.067 回答
2

您可以使用警卫来匹配您的交易:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | root :: fileName :: rest when root.ToUpper() = "/OUT" -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }
于 2010-02-09T02:32:02.183 回答
1

遇到这个寻找类似问题的解决方案,虽然 Tomas 的解决方案适用于单个字符串,但它对原始字符串列表的模式匹配问题没有帮助。他的活动模式的修改版本允许匹配列表:

let (|InvariantEqual|_|) : string list -> string list -> unit option =
    fun x y ->
        let f : unit option -> string * string -> unit option =
            fun state (x, y) ->
                match state with
                | None -> None
                | Some() ->
                    if x.Equals(y, System.StringComparison.OrdinalIgnoreCase)
                    then Some()
                    else None
        if x.Length <> y.Length then None
        else List.zip x y |> List.fold f (Some())

match ["HeLlO wOrLd"] with
| InvariantEqual ["hello World";"Part Two!"] -> printfn "Bad input"
| InvariantEqual ["hello WORLD"] -> printfn "World says hello"
| _ -> printfn "No match found"

我还没有弄清楚如何让它与占位符正确匹配| InvariantEqual "/out" :: fileName :: rest -> ...,但如果你知道列表的全部内容,这是一个改进。

于 2015-03-11T16:09:25.477 回答