Нечувствительное к регистру сопоставление шаблонов со списками строк - PullRequest
17 голосов
/ 09 февраля 2010

Я пытаюсь разобрать аргументы командной строки в приложении 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 ]

28 голосов
/ 09 февраля 2010

Мне очень нравится ваша идея использовать активные шаблоны 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!"    

Вы правы, что это более многословно, но приятно скрывает логику и дает вам достаточно силы, чтобы использовать рекомендуемый стиль кодирования (я не уверен, как это можно сделать с помощью предварительной обработки).

2 голосов
/ 09 февраля 2010

Вы можете использовать охранников, чтобы соответствовать вашей сделке:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | root :: fileName :: rest when root.ToUpper() = "/OUT" -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }
2 голосов
/ 09 февраля 2010

Я мог бы выполнить некоторую предварительную обработку, чтобы учесть либо "-", либо "/" в начале ключевых слов, и нормализовать регистр:

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

Возможно, это не идеал, но не то, чтобы у любого пользователя хватило терпения набирать столько параметров командной строки, что их повторение в цикле дважды происходит заметно медленнее.

1 голос
/ 11 марта 2015

Наткнулся на это в поисках решения аналогичной проблемы, и хотя решение 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 -> ..., но если вы знаете все содержимое списка, это улучшение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...