Подтвердить что ты не робот

Нечувствительный к шаблону шаблон, соответствующий строковым спискам

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 3

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

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

Ответ 4

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