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