Сопоставление шаблонов без учета регистра в списках строк

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

Есть ли лучший вариант/шаблон для таких вещей? Я думаю, что это общая проблема, и должен быть хороший способ справиться с ней.


person mmx    schedule 08.02.2010    source источник


Ответы (4)


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

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

person Tomas Petricek    schedule 09.02.2010
comment
Я считаю, что имя InvariantEqual вводит в заблуждение, поскольку вы используете OrdinalIgnoreCase, а не InvariantCultureIgnoreCase. - person Maslow; 21.04.2015

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

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

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

person Joel Mueller    schedule 08.02.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 }
person ssp    schedule 09.02.2010

Столкнулся с этим в поисках решения аналогичной проблемы, и хотя решение Томаса работает для отдельных строк, оно не помогает с исходной проблемой сопоставления шаблонов со списками строк. Модифицированная версия его активного шаблона позволяет сопоставлять списки:

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

person Amazingant    schedule 11.03.2015