我正在尝试解析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"
之类的事情。 有没有更好的选择/模式来做这些事情?我认为这是一个常见的问题,应该有一个很好的方法来处理它。
答案 0 :(得分:28)
我非常喜欢使用F#活动模式来解决这个问题。它比使用预处理更冗长,但我认为它非常优雅。此外,根据some BCL guidelines,您在比较字符串时不应使用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!"
你是对的,它更加冗长,但它很好地隐藏了逻辑,并且它为你提供了足够的能力来使用推荐的编码风格(我不确定如何使用预处理来完成)。
答案 1 :(得分:2)
我可能会做一些预处理,在关键字的开头允许“ - ”或“/”,并规范化案例:
let normalize (arg:string) =
if arg.[0] = '/' || arg.[0] = '-' then
("-" + arg.[1..].ToLower())
else arg
let normalized = args |> List.map normalize
它可能并不理想,但它并不像任何用户都有足够的耐心来输入这么多的命令行参数,循环它们两次显然很慢。
答案 2 :(得分: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 }
答案 3 :(得分: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 -> ...
,但如果你知道列表的全部内容,那就是一种改进。